✔︎ Last updated on April 7th, 2024
Applying certain styles temporarily is a good way to give your user a visual feedback. In this article, I will explain how you can apply the flash style on an element to let the user know that a certain key was pressed or a certain element was detected.
The other day, I was building a typing-based app in Vue. Part of the app was an on-screen keyboard containing all the keys for the English alphabet. My goal was to make the onscreen keys “flash” when the user presses them on the keyboard. The keys after flashing briefly will quickly go back to normal.
The first thought in my mind was to create a data property called isFlashed
and bind a class to it dynamically, like this —
<span :class="['key', {flash: isFlashed}]">
{{ Keyname }}
</span>
However, I quickly realized that to make this work for all 26 keys, I’d need to track the isFlashed
state for each key independently. This meant turning the keys into their own components. Since my app was simple, I didn’t want to overcomplicate things by creating a bunch of components just for this feature.
Must read: How to Lighten or Darken a Color with Pure CSS
Thankfully, I realized I didn’t need to convert them to components. JavaScript itself has a built-in tool called setTimeout
which is perfect for my needs. It lets you run a bit of code after a set amount of time, which was perfect for my flashing key effect!
Set up
This is the code we are starting with:
HTML
<div id="app">
<div class="keyboard-container">
<div class="keyboard-row">
<span v-for="key in row1" :key="key" class="key">{{ key }}</span>
</div>
<div class="keyboard-row">
<span v-for="key in row2" :key="key" class="key">{{ key }}</span>
</div>
<div class="keyboard-row">
<span v-for="key in row3" :key="key" class="key">{{ key }}</span>
</div>
</div>
</div>
CSS
.keyboard-container {
padding: 80px;
display: grid;
gap: 5px;
}
.keyboard-row {
display: flex;
justify-content: center;
}
.key {
padding: 12px 15px;
border: 1px solid #ccc;
border-radius: 5px;
user-select: none; /* Prevent text selection */
margin: 2px;
background: whitesmoke;
}
JavaScript
app = Vue.createApp({
data() {
return {
row1: ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
row2: ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
row3: ["Z", "X", "C", "V", "B", "N", "M"],
};
},
});
app.mount("#app");
We are storing the key names in three arrays, one for each row of the keyboard. These keys are then displayed on screen using the v-for
directive in Vue. The keys are then styled using the CSS declarations shown above (👆).
Also read: How to Make a Radar Scanner Animation in CSS (Using pseudo-elements)
This code will give you this keyboard layout:
The next step is to make the on-screen keyboard actually react when the user presses a key on their physical keyboard. Since pressing a key is called a keydown
event, we need to set up a special “listener” for that event.
We attach this listener to the window
object instead of the individual keys for a few reasons. When using these keydown
events, the element you’re listening to needs to be focused. Web browsers doens’t allow for more than one element to be focused at the same time.
Also read: How to Read the New Yorker and Atlantic articles for FREE
However, the window
object represents the entire browser window, which is always focused as long as the user is on the page. Therefore we attach the listener to the window object.
Adding an Event Listener on the window object
In Vue.js, here’s how you add a “keydown” listener to the window object (this code goes inside the <script>
part of your Vue file):
<script>
mounted() {
this.attachKeydownListener(); // Call the method on mount
},
methods: {
attachKeydownListener() {
window.addEventListener("keydown", (e) => {
// handle event here
});
}
}
</script>
In the mounted hook, we are calling the function which attaches the event listener to the window object. We also could have added the event listener in the mounted hook itself and then created a named function in the methods
to handle this logic. Both approaches work.
Now, we only want to process the letter keys, not any other key such as control, alt, space or meta
key, so we need a regex which will filter out such keys and allow us to handle only the English alphabet letters.
This is how our attachKeydownListener
function looks like when we use the regex filter:
attachKeydownListener() {
window.addEventListener("keydown", (e) => {
const alphabetRegex = /^[A-Z]$/;
const key = e.key.toUpperCase();
// match only if a letter is pressed
if (alphabetRegex.test(key)) {
// apply flashing styles here
}
});
}
Now that we have our key, its time to apply the flashing style. For this, we will use the ref
attribute provided natively by Vue.
Vue maintains a list of all elements which declare a ref
attribute in their declarations which allows us to refer to any element using the array:
this.$refs.id
We have to provide a unique identifier (id) to each element which we want to access using the $refs
array. This works just like JavaScript’s native document.getElementById("#id")
method.
But first, we need to provide this attribute to all the keys that we are putting on the onscreen keyboard. We have to modify our html code, especially, the v-for
directive. This is what the updated code looks like:
<div class="keyboard-container">
<div class="keyboard-row">
<span v-for="key in row1" :key="key" :ref="key" class="key">{{ key }}</span>
</div>
<div class="keyboard-row">
<span v-for="key in row2" :key="key" :ref="key" class="key">{{ key }}</span>
</div>
<div class="keyboard-row">
<span v-for="key in row3" :key="key" :ref="key" class="key">{{ key }}</span>
</div>
</div>
Observe that we have added :ref="key"
attribute to each of the v-for
declaration in our HTML template.
Now, all we need to do is use some javascript logic to first add the styles, and then remove them after some time, say 100 milliseconds. The native built-in function setTimeout
is perfect for this type of task.
Must read: 4 Awesome tricks to Change the Default Fonts in Excalidraw
Here’s how you can add this code to the attachKeydownListener
function to temporarily change the background color to lightgreen
: —
attachKeydownListener() {
window.addEventListener("keydown", (e) => {
const alphabetRegex = /^[A-Z]$/;
const key = e.key.toUpperCase();
// match only if a letter is pressed
if (alphabetRegex.test(key)) {
// apply style on that key
this.$refs[key][0].style.backgroundColor = "lightgreen";
setTimeout(() => {
// remove this style after a 100ms
this.$refs[key][0].style.backgroundColor = "whitesmoke";
}, 100);
}
});
}
Now your onscreen keys will flash when they are pressed. To make the transition smooth, you may also want to declare a transition duration in your CSS styles on keys so that the flashy effect isn’t too harsh for the user:
.key {
transition: 0.1s;
}
For a seamless transition, keep the transition duration same as the setTimeout duration, but it isn’t strictly required.
You can see the final program here on Codepen:
<div id="app"> <div class="keyboard-container"> <div class="keyboard-row"> <span v-for="key in row1" :key="key" :ref="key" class="key">{{ key }}</span> </div> <div class="keyboard-row"> <span v-for="key in row2" :key="key" :ref="key" class="key">{{ key }}</span> </div> <div class="keyboard-row"> <span v-for="key in row3" :key="key" :ref="key" class="key">{{ key }}</span> </div> </div> <p> Wanna know how I did this? Check out this <a href="https://csswolf.com/how-to-style-an-element-temporarily-using-vue/"> blog post on CssWolf.com.</a> </div>
body { font-family: whitney, avenir, system-ui; } .keyboard-container { display: grid; gap: 5px; } .keyboard-row { display: flex; justify-content: center; } .key { padding: 12px 15px; border: 1px solid #ccc; border-radius: 5px; user-select: none; /* Prevent text selection */ margin: 2px; background: whitesmoke; transition: 0.1s; } #app, p { text-align: center; padding: 40px; }
app = Vue.createApp({ data() { return { row1: ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], row2: ["A", "S", "D", "F", "G", "H", "J", "K", "L"], row3: ["Z", "X", "C", "V", "B", "N", "M"], flash: false, text: "" }; }, mounted() { this.attachKeydownListener(); // Call the method on mount }, methods: { attachKeydownListener() { window.addEventListener("keydown", (e) => { const alphabetRegex = /^[A-Z]$/; const key = e.key.toUpperCase(); // match only if a letter is pressed if (alphabetRegex.test(key)) { // apply style on that key this.$refs[key][0].style.backgroundColor = "lightgreen"; setTimeout(() => { // remove this style after a 100ms this.$refs[key][0].style.backgroundColor = "whitesmoke"; }, 100); } }); } } }); app.mount("#app");
That’s it guys. You can experiment a little more with other approaches, such as adding and detaching a class after a duration, applying animation directly on a keypress or something more innovative.
In my experience, I have found it working excellent for my use case.
Thanks for reading.