Tab Scroll
-
I've been trying to emulate a new Opera feature as described here: https://forum.vivaldi.net/topic/26784/return-to-previous-position-after-scroll-to-top. The mod scrolls to top of the current page when clicking a tab and scrolls back to initial position when clicking the tab again. You will need a
custom.js
file to implement this modification. Instructions: https://forum.vivaldi.net/topic/10549/modding-vivaldi/. Please disable the "minimise active tab" feature invivaldi://settings/tabs/
, or the mod won't work as intended. You can only use one or the other.
custom.js
// Tab Scroll // version 2024.9.1 // https://forum.vivaldi.net/post/214898 // Clicking on an active tab scrolls page to top, clicking it again returns to // previous scroll position. Credits to tam710562 from Vivaldi Forum for coming // up with the sessionStorage solution, which made this possible. (function tabScroll() { "use strict"; // EDIT START // choose scroll behavior, instant or smooth const scb = "instant"; // EDIT END function exit(tab) { tab.removeEventListener("mousemove", exit); tab.removeEventListener("click", trigger); } function trigger(tab) { chrome.scripting.executeScript({ target: { tabId: Number(tab.parentNode.parentNode.id.replace(/\D/g, "")), }, func: script, args: [scb], }); exit(tab); } function react(e, tab) { if ( tab.parentNode.classList.contains("active") && e.which === 1 && !(e.target.nodeName === "path" || e.target.nodeName === "svg") && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey ) { tab.addEventListener("mousemove", exit(tab)); tab.addEventListener("click", trigger(tab)); } } const script = (scb) => { let offset = window.scrollY; if (offset > 0) { window.sessionStorage.setItem("offset", offset); window.scrollTo({ top: 0, behavior: scb }); } else { window.scrollTo({ top: window.sessionStorage.getItem("offset") || 0, behavior: scb, }); } }; let appendChild = Element.prototype.appendChild; Element.prototype.appendChild = function () { if ( arguments[0].tagName === "DIV" && arguments[0].classList.contains("tab-header") ) { setTimeout( function () { const ts = (event) => react(event, arguments[0]); arguments[0].addEventListener("mousedown", ts); }.bind(this, arguments[0]) ); } return appendChild.apply(this, arguments); }; })();
Opera blog post with feature announcement β https://blogs.opera.com/desktop/2018/02/opera-51/
History
- 2019-07-02: Back to URL independent version, because it works on all sites (especially Vivaldi Forum). For the other version look deeper into this topic
- 2021-02-18: Fixes for 3.7, works with 2 level tab stacks now
- 2021-07-10: Use scripting.executescript
- 2022.4.0 Update functions
- 2024.9.0 Fix for 6.10, use instant scrolling by default, donβt trigger on close button
- 2024.9.1 Provide a constant to set instant or smooth scroll behavior
-
@luetage Nice, testing.
Good idea. I think would better a hover button to scroll up or use the favicon to do that and prevent accidental scroll-to-top.But it works
-
@hadden89 Would be far easier to do this with a dedicated button, but the point was to emulate the request. I'm not too concerned about accidental clicking, because why should you click the title of an active tab, if you didn't want to use the functionality to go to top? And even if you click it by accident, you can just return to the previous position by clicking again
-
I found out that I can just put the scrollTab.js code into custom.js. It's far easier to use one file, especially if you are using a bash script to reload the files on update. OP has been updated.
-
Thanks for making this. I have learned interesting things:
- You can use
chrome.*
APIs in the context of achrome.tabs.executeScript()
. I guess chrome treats the executed script like a content script. - The callback function can take in the output of execution of the script. I guess I completely skipped over that part in the docs.
- You can use
-
@lonm Yeah, I was surprised too. Especially that it's so easy to use the local storage when needed. The callback function from the execute script part was a fluke, I found it on stackoverflow by accident. I really wanted to avoid using multiple files and passing messages.
-
This mod stopped working on the latest snapshot (2.0), because Vivaldi registers a click on a tab with target
tab-header
now. Therefore the script could be simplified and it's easier triggering the function.OP has been updated.
-
Mod had problems with the latest update on snapshot, click on the title is being recognized as title again, not tab-header. I updated the script in OP, hopefully this version is somewhat more change/future-proof.
-
@luetage I think it's recommended to use window.sessionStorage or a variable in chrome.tabs.executeScript instead of using chrome.storage.local. It will save the scroll bar position by tab instead of only one value in multiple tabs
function tabScroll(event) { var target = event.target; if (target.parentNode.classList.contains('tab-header')) { target = target.parentNode; } if (target.classList.contains('tab-header')) { chrome.tabs.executeScript({ code: 'var offset=window.pageYOffset;if(offset>0){window.sessionStorage.setItem("vivaldi-offset",offset);window.scrollTo(0,0);}else{window.scrollTo(0,window.sessionStorage.getItem("vivaldi-offset")||0);}' }); } } // Loop waiting for the browser to load the UI. You can call all functions from just one instance. setTimeout(function wait() { const browser = document.getElementById('browser'); if (browser) { document.body.addEventListener('click', tabScroll); } else { setTimeout(wait, 300); } }, 300);
-
@tam710562 I had no idea about session storage. This is pretty cool β of course it's better when everything is set for each tab individually β this brings it almost to feature parity with the native Opera version.
-
@tam710562 The current problem is that the tabScroll triggers on selecting a tab, when it really should only trigger on an active tab. The click event listener can't catch this, but gladly
mousedown
can.function tabScroll(event) { var target = event.target; if (target.parentNode.classList.contains('tab-header')) { target = target.parentNode; } if (target.classList.contains('tab-header') && target.parentNode.classList.contains('active')) { chrome.tabs.executeScript({ code: 'var offset=window.pageYOffset;if(offset>0){window.sessionStorage.setItem("vivaldi-offset",offset);window.scrollTo(0,0);}else{window.scrollTo(0,window.sessionStorage.getItem("vivaldi-offset")||0);}' }); } }; setTimeout(function wait() { const browser = document.getElementById('browser'); if (browser) { document.body.addEventListener('mousedown', tabScroll); } else { setTimeout(wait, 300); } }, 300);
I believe this is feature parity to the Opera version now. If you have more improvements, I'd be naturally interested β I've been using the initial version for months now and am a fan of this functionality. Imo all browsers should have it.
-
@luetage I've just installed the mod, it's an interesting feature.
Minimise active tab
in Tabs Settings has to be disabled to make it work (just an info for anyone wanted to test it). -
@luetage Sorry for not considering this issue
It seems that vivaldi'sclick
event is pre-activated somousedown
is a good measure in this case. -
@hlehyaric Yeah, minimise active tab changes tab when you just want to go to top or previous. Personally I never had it activated, it's far more comfortable switching tabs with shortcut. But good point.
-
Seems like there are a few more problems I have previously overlookedβ¦
- The
mousedown
event triggers on right click too. - In Opera the scroll is triggered on click (
mouseup
left button). - When a modifier key is being used the event is triggered, but we don't intend to trigger the scroll when we want to select tabs with
shift
, view the context menu withctrl
, etc. - The event triggers when a tab is being moved.
I tried to address all of these and it seems to behave fine. But please do test this, maybe more adjustments are needed.
function tabScrollExit() { tsTarget.removeEventListener('mousemove', tabScrollExit); tsTarget.removeEventListener('click', tabScrollTrigger); }; function tabScrollTrigger() { chrome.tabs.executeScript({ code: 'var offset=window.pageYOffset;if(offset>0){window.sessionStorage.setItem("tabOffset",offset);window.scrollTo(0,0);}else{window.scrollTo(0,window.sessionStorage.getItem("tabOffset")||0);}' }); tabScrollExit(); }; function tabScroll(event) { if (event.which == 1 && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { tsTarget = event.target; if (tsTarget.parentNode.classList.contains('tab-header')) { tsTarget = tsTarget.parentNode; } if (tsTarget.classList.contains('tab-header') && tsTarget.parentNode.classList.contains('active')) { tsTarget.addEventListener('mousemove', tabScrollExit); tsTarget.addEventListener('click', tabScrollTrigger); } } }; setTimeout(function wait() { const browser = document.getElementById('browser'); if (browser) { document.body.addEventListener('mousedown', tabScroll); } else { setTimeout(wait, 300); } }, 300);
- The
-
@luetage Thanks for this mod and your quick fixes.
I found a problem, it also exists in Opera version. It is the opening of a link in the current tab of the same domain that still remembers the tab location saved by the previous tab (This doesn't affect much, but I really don't like it ).- I added a url variable to determine if the link is saved and the current link is the same, which makes it work differently than the Opera version.
- Add
(function () {/ * code * /}) ();
to avoid overwriting with Vivaldi functions or other mods.
(function () { function tabScrollExit() { tsTarget.removeEventListener('mousemove', tabScrollExit); tsTarget.removeEventListener('click', tabScrollTrigger); } const tabScrollScript = '!' + function () { var tabOffset = JSON.parse(window.sessionStorage.getItem('tabOffset')) || {}; var offset = window.pageYOffset; var urlWithoutHash = document.URL.replace(/#.*$/, ''); if (offset > 0) { window.scrollTo(0, 0); tabOffset = { offset: offset, url: urlWithoutHash }; window.sessionStorage.setItem('tabOffset', JSON.stringify(tabOffset)); } else if (urlWithoutHash === tabOffset.url) { window.scrollTo(0, tabOffset.offset); } } + '();'; function tabScrollTrigger() { chrome.tabs.executeScript({ code: tabScrollScript }); tabScrollExit(); } function tabScroll(event) { if (event.which == 1 && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { tsTarget = event.target; if (tsTarget.parentNode.classList.contains('tab-header')) { tsTarget = tsTarget.parentNode; } if (tsTarget.classList.contains('tab-header') && tsTarget.parentNode.classList.contains('active')) { tsTarget.addEventListener('mousemove', tabScrollExit); tsTarget.addEventListener('click', tabScrollTrigger); } } } setTimeout(function wait() { const browser = document.getElementById('browser'); if (browser) { document.body.addEventListener('mousedown', tabScroll); } else { setTimeout(wait, 300); } }, 300); })();
I hope it will make the mod better.
-
@tam710562 It definitely makes the mod better. You're fixing problems I never knew we had, which is a good thing. And apparently this versions is now superior to the Chinese Opera version, so job well done.
You're good at coding modifications, I hope you stick around. By the way, although it doesn't really matter⦠if you do have a github account and you care for such things, please make a pull request for your improvements. I'm trying to put all my mods up on github too and you naturally deserve credit for all of these contributions.
-
@luetage Yeah, I created a pull request for my improvements.
-
This mod ceases to function on Vivaldi version 3.7 (current snapshot). As found out by @LonM
chrome.tabs.executeScript
isnβt available anymore from the UI. Moreover Vivaldi switched from triggering the active tab on click, to triggering it on mousedown, which hurts this mod too. I donβt see what to do about either of these issues at this point in time.Workaround: Switch to Opera browser immediately.
GMO Iβm joking you guys, relax. Vimium provides the functionality to set local marks on webpages, which allows to jump to top and back too. Moreover we can set more than one mark, so itβs actually more advanced, just keyboard driven.
-
@luetage Thanks to @tam710562 this mod works again, see here β https://forum.vivaldi.net/topic/57191/restore-methods-for-chrome-tabs. You will have to insert the code in your modfile, then executing scripts will work as intended on webpages. Additionally I rewrote the mod to work with 2 level tab stacks and apparently we lucked out: The mousedown change implemented by Vivaldi in the latest version doesnβt influence this mod. It seems like the mousedown listener in this script is faster than the switching of tabs in Vivaldi. To make a short story longer: all functionality is restored and the mod should work as expected.