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>

Sᴀᴍ Onᴇᴌᴀ
29.6k16 gold badges45 silver badges203 bronze badges
asked Jun 20, 2024 at 12:35
\$\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\$ Commented Jun 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\$ Commented Jun 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\$ Commented Jun 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>

answered Aug 1, 2024 at 13:36
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.