Currently, I am working on creating a typing game that is reminiscent of monkeytype.com. In this game, every letter is linked to classes that change dynamically from an empty string to either 'correct' or 'incorrect', depending on whether the user's input matches the expected letter.
One issue I'm facing is related to how Svelte handles unused classes - the 'correct' and 'incorrect' classes may not exist in the DOM right away. This makes it challenging for me to apply specific styles to these letter classes when they become active.
Below is a snippet of my code:
<script lang="ts">
type Game = 'waiting for game' | 'in progress' | 'game over';
type Word = string;
let game: Game = 'waiting for game';
let typedLetter = '';
let words: Word[] = 'the quick brown fox jumped over the lazy dog'.split(' ');
let wordIndex = 0;
let letterIndex = 0;
let correctLetter = 0;
let correctLetters = 0;
let wordsEl: HTMLDivElement;
let letterEl: HTMLSpanElement;
let inputEl: HTMLInputElement;
function startGame() {
setGameState('in progress');
}
function setGameState(state: Game) {
game = state;
}
function updateGameState() {
setLetter();
checkLetter();
nextLetter();
resetLetter();
}
function setLetter() {
const isWordCompleted = letterIndex > words[wordIndex].length - 1;
if (!isWordCompleted) {
letterEl = wordsEl.children[wordIndex].children[letterIndex] as HTMLSpanElement;
}
}
function checkLetter() {
const currentLetter = words[wordIndex][letterIndex];
if (typedLetter === currentLetter) {
letterEl.dataset.letter = 'correct';
increaseScore();
}
if (typedLetter !== currentLetter) {
letterEl.dataset.letter = 'incorrect';
}
}
function increaseScore() {
correctLetters += 1;
}
function nextLetter() {
letterIndex += 1;
}
function resetLetter() {
typedLetter = '';
}
function handleKeydown(event: KeyboardEvent) {
if (event.code === 'Space') {
event.preventDefault();
}
if (game === 'waiting for game') {
startGame();
}
}
</script>
<div class="game" data-game={game}>
<input
type="text"
class="input"
bind:this={inputEl}
bind:value={typedLetter}
on:input={updateGameState}
on:keydown={handleKeydown}
/>
</div>
<div class="words" bind:this={wordsEl}>
{#each words as word}
<span class="word">
{#each word as letter}
<span class="letter">{letter}</span>
{/each}
</span>
{/each}
</div>
<style lang="scss">
.words {
--line-height: 1em;
--lines: 3;
width: 100%;
max-height: calc(var(--line-height) * var(--lines) * 1.42);
display: flex;
flex-wrap: wrap;
gap: 0.6em;
position: relative;
font-size: 1.5rem;
line-height: var(--line-height);
overflow: hidden;
user-select: none;
.letter {
opacity: 0.4;
transition: all 0.3s ease;
&:global([data-letter='correct']) {
opacity: 0.8;
}
&:global([data-letter='incorrect']) {
color: var(--primary);
opacity: 1;
}
}
}
</style>
I've made an attempt to use :global for the data attributes, but I encountered an error message stating ':global(...) not at the start of a selector sequence should not contain type or universal selectors svelte(css-invalid-global-selector-position)'. All packages and Svelte are fully up to date.