I am new to JavaScript.
Context: I created a bookmarklet (a browser bookmark that executes JavaScript instead of opening a webpage) that creates a "mousedown" event listener. When the listener is called it determines what element is under the mouse pointer, then if the element is text it changes the text color, alerts the user with some information, and when the alert is over reverts the text to its original color. Here is the basic code:
document.addEventListener('mousedown', e => {
const element = document.elementFromPoint(e.clientX, e.clientY);
if (element is text) {
const originalBackgroundColor = getComputedStyle(element).backgroundColor;
const originalColor = getComputedStyle(element).color;
element.style.backgroundColor = "yellow";
element.style.color = "black";
alert(some text);
element.style.backgroundColor = originalBackgroundColor;
element.style.color = originalColor;
}
})
Problem: Although the element style is updated before the alert is called, it doesn't appear to update on screen before the alert is displayed, so you cannot see a change.
I determined that there must be some delay between when the style is updated in the code and when it actually updates on-screen, so I tried implementing a setTimeout() function to combat this:
element.style.backgroundColor = "yellow";
element.style.color = "black";
setTimeout(() => {
alert(some text);
element.style.backgroundColor = originalBackgroundColor;
element.style.color = originalColor;
}, 0)
This seems to work about 90% of the time, but occasionally some text will not visually update in time before the alert is displayed. I tried upping the delay time on the setTimeout() function to 10ms, which still doesn't always work. When I change it to 20ms it seems to always work, and I have determined that it is most likely because the screen refreshes every ~17ms (60fps), so a 20ms delay nearly guarantees the screen has refreshed before the alert is called. However, just having a 20ms delay seems arbitrary and there is also a somewhat noticeable delay between when the text is clicked and when the alert is displayed.
Is there a better way to do this? Perhaps a way to wait exactly one frame before calling the alert? Or force the text to update visually before the alert is called? Any help would be greatly appreciated.
You might want to look into requestAnimationFrame()
. From MDN:
This will request that your animation function be called before the browser performs the next repaint
You want to run your code after the next repaint, so you can nest a setTimeout(..., 0)
call within your requestAnimationFrame
. For example:
document.addEventListener('mousedown', e => {
const element = document.elementFromPoint(e.clientX, e.clientY);
if (element is text) {
const originalBackgroundColor = getComputedStyle(element).backgroundColor;
const originalColor = getComputedStyle(element).color;
element.style.backgroundColor = "yellow";
element.style.color = "black";
requestAnimationFrame(() => {
setTimeout(() => {
alert(some text);
element.style.backgroundColor = originalBackgroundColor;
element.style.color = originalColor;
}, 0)
})
}
})