5
\$\begingroup\$

I recently recalled having a "scroll mark" feature, that is, a line that would show up when hitting the space bar in the Opera browser? There used to be a Greasemonkey script for Firefox, but even when you look for a copy, it's defunct and I couldn't find a working one, possibly because I can't convince the search engine to search for the right thing.

Screenshot to get an idea:

screenshot

In any case, below is a quick solution that I drafted and given my lack of JavaScript/CSS/browser-fu, I'd like to get some feedback and potential improvements for this, in particular:

  • is there a (much) better way to construct the marker line?
  • are width and height correctly calculated in all (or most) circumstances?
  • is there a better (short code, or more performant) way to describe the animation?
  • any other opportunities to skip updates when the bar isn't shown / off-screen?
  • is there a better way to make this behave well when scrolling with the mouse wheel, like doing a debounce step and only showing it after a short timeout, or is there another DOM feature that would allow me to only activate when a full page has been scrolled (i.e. hitting space bar)?

My plan is then to perhaps convert it to an extension as well, but that's a future review.

Feature-wise, this does what I personally want, but I could see the need for:

  • disabling the fade animation,
  • entirely disabling the automatic hiding of the marker and having a keyboard shortcut to toggle it manually,
  • colour, size maybe, anything else?

The demo here doesn't do justice to the fading effect, at least on my system it doesn't properly show, you'd have to load it into Tampermonkey to see it.

(Updated to also show on scrolling up, since nobody had answered yet.)

// ==UserScript== // @name Page Scroll Marker // @namespace https://macrolet.net/ // @version 2024-06-20 // @description try to take over the world! // @author You // @match *://*/* // @icon https://www.google.com/s2/favicons?sz=64&domain=userscripts-mirror.org // @grant none // ==/UserScript== (function() { "use strict" let bar = null let previousPosition = window.scrollY function upsertBar() { if (bar === null) { bar = document.createElement("div") bar.style.zIndex = "2147483647" bar.style.width = window.innerWidth + "px" bar.style.height = "2px" bar.style.background = "red" bar.style.position = "absolute" bar.style.left = "0px" document.body.appendChild(bar) } if (window.scrollY > previousPosition) { bar.style.top = (previousPosition + window.innerHeight - 2) + "px" } else { bar.style.top = (previousPosition - 2) + "px" } bar.style.opacity = "1" setTimeout(function() { bar.style.transition = "opacity 0.5s ease-in-out" }, 0) setTimeout(function() { bar.style.opacity = "0" }, 500) setTimeout(function() { bar.style.transition = "" }, 1000); previousPosition = window.scrollY } document.addEventListener("scrollend", function (event) { upsertBar() }) })()
<pre> 1 Lorem ipsum dolor 2 sit amet, 3 consectetur 4 adipiscing elit, sed 5 do eiusmod tempor 6 incididunt ut labore 7 et dolore magna 8 aliqua. Ut enim ad 9 minim veniam, quis 10 nostrud exercitation 11 ullamco laboris nisi 12 ut aliquip ex ea 13 commodo 14 consequat. Duis aute 15 irure dolor in 16 reprehenderit in 17 voluptate velit esse 18 cillum dolore eu 19 fugiat nulla 20 pariatur. Excepteur 21 sint occaecat 22 cupidatat non 23 proident, sunt in 24 culpa qui officia 25 deserunt mollit anim 26 id est laborum. 27 28 Lorem ipsum dolor 29 sit amet, 30 consectetur 31 adipiscing elit, sed 32 do eiusmod tempor 33 incididunt ut labore 34 et dolore magna 35 aliqua. Ut enim ad 36 minim veniam, quis 37 nostrud exercitation 38 ullamco laboris nisi 39 ut aliquip ex ea 40 commodo 41 consequat. Duis aute 42 irure dolor in 43 reprehenderit in 44 voluptate velit esse 45 cillum dolore eu 46 fugiat nulla 47 pariatur. Excepteur 48 sint occaecat 49 cupidatat non 50 proident, sunt in 51 culpa qui officia 52 deserunt mollit anim 53 id est laborum. </pre>

\$\endgroup\$
3
  • 1
    \$\begingroup\$Very nice, thank you. Though I confess to seeing some flicker / redrawing when using the usual trackpad vertical scrolling gestures within Firefox. The PageDown key appears to be identical to SPACE, and both "d" (think /usr/bin/less "down") and PageUp seem to be ignored. My understanding of the UX is that you're trying to help the user quickly resume reading after a big scroll operation. Maybe it would demo a little better with a bunch of Lorem Ipsum, plus line numbers running down the left margin?\$\endgroup\$
    – J_H
    CommentedJun 20, 2024 at 16:37
  • \$\begingroup\$Done, thanks. The flickering isn't quite as bad when running it as a user script. Though I'm sure this solution as-is isn't the best way anyway, thus the post.\$\endgroup\$
    – ferada
    CommentedJun 20, 2024 at 20:22
  • \$\begingroup\$You're right, I completely skipped the scrolling up part, that would make sense to have it insert the bar at the top in that case. Have to add it.\$\endgroup\$
    – ferada
    CommentedJun 20, 2024 at 20:24

1 Answer 1

3
+100
\$\begingroup\$

Review

This seems to work acceptably and I don't have much to offer in terms of improving the styles or animation of the marker. I used to use Opera regularly in the past and vaguely remember the feature.

Consider constructing the marker line with a Horizontal Rule element

In terms of constructing the marker line - one could use a The Thematic Break (Horizontal Rule) element <hr> instead of a Content Division element <div>. The <hr> element has a specific attribute for color which can be used instead of the background attribute for the <div>.

Consider using arrow functions to simplify timeout callbacks

One could also simplify the functions passed to setTimeout() - e.g.

setTimeout(function() { bar.style.transition = "opacity 0.5s ease-in-out" }, 0) 

Could be simplified to:

setTimeout(() => { bar.style.transition = "opacity 0.5s ease-in-out" }, 0) 

Consider using requestAnimationFrame() instead of setTimeout()

As this post by Blindman67 explains:

requestAnimationFrame is still the best option rather than setTimeout and setInterval.

The main reason is that requestAnimationFrame is synced to the display refresh rate.

Specifically the vertical blank, vertical sync, or vSync (analogous to old CRT displays). This is a time in the display process that pixels on the display are not being updated and changes can be made to VRAM while not affecting the displayed pixels.

requestAnimationFrame will call your render function well before the next vSync and after the previous vSync. When your render function returns requestAnimationFrame will wait till after the next vSync before calling your render function again (Be sure to return in time).

The three timers created with setTimeout() could be replaced with a single function (albeit likely longer) using window.requestAnimationFrame().

Event listener registration can be simplified

Since the function upsertBar() expects no parameters, the registration can be simplified from:

document.addEventListener("scrollend", function (event) { upsertBar() }) 

to simply passing a reference to the function as the listener parameter:

document.addEventListener("scrollend", upsertBar) 

Consider updating userscript metadata block

It appears the metadata block contains default values for some keys - like @description and author. While it isn't imperative that those be changed, changing them may be helpful for others.

// ==UserScript== // @name Page Scroll Marker // @namespace https://macrolet.net/ // @version 2024-06-20 // @description try to take over the world! // @author You // @match *://*/* // @icon https://www.google.com/s2/favicons?sz=64&domain=userscripts-mirror.org // @grant none // ==/UserScript== (function() { "use strict" let bar = null let previousPosition = window.scrollY function upsertBar() { if (bar === null) { bar = document.createElement("div") bar.style.zIndex = "2147483647" bar.style.width = window.innerWidth + "px" bar.style.height = "2px" bar.style.background = "red" bar.style.position = "absolute" bar.style.left = "0px" document.body.appendChild(bar) } if (window.scrollY > previousPosition) { bar.style.top = (previousPosition + window.innerHeight - 2) + "px" } else { bar.style.top = (previousPosition - 2) + "px" } bar.style.opacity = "1" let start, previousTimeStamp function step(timeStamp) { console.log('ste') if (start === undefined) { start = timeStamp } const elapsed = timeStamp - start if (elapsed > 500) { bar.style.opacity = "0" } if (elapsed < 1000) { bar.style.transition = "opacity 0.5s ease-in-out" // Stop the animation after 1 seconds previousTimeStamp = timeStamp window.requestAnimationFrame(step) } else { bar.style.transition = "" } } window.requestAnimationFrame(step) previousPosition = window.scrollY } document.addEventListener("scrollend", upsertBar) })()
<pre> 1 Lorem ipsum dolor 2 sit amet, 3 consectetur 4 adipiscing elit, sed 5 do eiusmod tempor 6 incididunt ut labore 7 et dolore magna 8 aliqua. Ut enim ad 9 minim veniam, quis 10 nostrud exercitation 11 ullamco laboris nisi 12 ut aliquip ex ea 13 commodo 14 consequat. Duis aute 15 irure dolor in 16 reprehenderit in 17 voluptate velit esse 18 cillum dolore eu 19 fugiat nulla 20 pariatur. Excepteur 21 sint occaecat 22 cupidatat non 23 proident, sunt in 24 culpa qui officia 25 deserunt mollit anim 26 id est laborum. 27 28 Lorem ipsum dolor 29 sit amet, 30 consectetur 31 adipiscing elit, sed 32 do eiusmod tempor 33 incididunt ut labore 34 et dolore magna 35 aliqua. Ut enim ad 36 minim veniam, quis 37 nostrud exercitation 38 ullamco laboris nisi 39 ut aliquip ex ea 40 commodo 41 consequat. Duis aute 42 irure dolor in 43 reprehenderit in 44 voluptate velit esse 45 cillum dolore eu 46 fugiat nulla 47 pariatur. Excepteur 48 sint occaecat 49 cupidatat non 50 proident, sunt in 51 culpa qui officia 52 deserunt mollit anim 53 id est laborum. </pre>

\$\endgroup\$
0

    Start asking to get answers

    Find the answer to your question by asking.

    Ask question

    Explore related questions

    See similar questions with these tags.