Greasy Fork is available in English.

Smoothscroll

Smooth scrolling on pages using javascript

  1. // ==UserScript==
  2. // @name Smoothscroll
  3. // @author Creec Winceptor
  4. // @description Smooth scrolling on pages using javascript
  5. // @namespace https://greatest.deepsurf.us/users/3167
  6. // @include *
  7. // @version 12
  8. // ==/UserScript==
  9.  
  10. var Smoothscroll = {};
  11.  
  12.  
  13. //settings
  14. Smoothscroll.Smoothness = 0.5;
  15. Smoothscroll.Acceleration = 0.5;
  16.  
  17.  
  18. //debug
  19. Smoothscroll.Debug = 0; //0-none, 1-some, etc.
  20.  
  21. /*
  22. //initial fps, no need to change
  23. Smoothscroll.BaseRefreshrate = 60;
  24. Smoothscroll.MaxRefreshrate = Smoothscroll.BaseRefreshrate*3;
  25. Smoothscroll.MinRefreshrate = Smoothscroll.BaseRefreshrate/3;
  26. */
  27.  
  28. //automatically calculated
  29. Smoothscroll.Refreshrate = 60;
  30.  
  31. Smoothscroll.MaxRefreshrate = 300;
  32. Smoothscroll.MinRefreshrate = 1;
  33.  
  34. //scrolling and animation
  35. function ScrollSubpixels(element, newvalue)
  36. {
  37. if (newvalue!=undefined)
  38. {
  39. element.scrollsubpixels = newvalue;
  40. return newvalue;
  41. }
  42. else
  43. {
  44. var olddelta = element.scrollsubpixels;
  45. if (olddelta!=undefined)
  46. {
  47. return olddelta;
  48. }
  49. return 0;
  50. }
  51. }
  52. function ScrollPixels(element, newvalue)
  53. {
  54. if (newvalue!=undefined)
  55. {
  56. element.scrollpixels = newvalue;
  57. ScrollSubpixels(element, 0);
  58. return newvalue;
  59. }
  60. else
  61. {
  62. var olddelta = element.scrollpixels;
  63. if (olddelta!=undefined)
  64. {
  65. return olddelta;
  66. }
  67. return 0;
  68. }
  69. }
  70.  
  71. var last = 0;
  72. function AnimateScroll(target, refreshrate) {
  73. var scrollsubpixels = ScrollSubpixels(target);
  74. var scrollpixels = ScrollPixels(target);
  75.  
  76. if (Smoothscroll.Debug>3) {
  77. console.log("scrollpixels: " + scrollpixels);
  78. }
  79. if (Smoothscroll.Debug>3) {
  80. console.log("target: ", target);
  81. if (target == document.documentElement) {
  82. console.log("document.documentElement");
  83. }
  84. }
  85.  
  86. var scrolldirection = 0;
  87. if (scrollpixels>0) {
  88. scrolldirection = 1;
  89. }
  90. if (scrollpixels<0) {
  91. scrolldirection = -1;
  92. }
  93.  
  94. var scrollratio = 1-Math.pow( refreshrate, -1/(refreshrate*Smoothscroll.Smoothness));
  95. var scrollrate = scrollpixels*scrollratio;
  96. if (Math.abs(scrollpixels)>2) {
  97. var fullscrolls = Math.floor(Math.abs(scrollrate))*scrolldirection;
  98. var scrollsubpixelsadded = scrollrate - fullscrolls;
  99.  
  100. var additionalscrolls = Math.floor(Math.abs(scrollsubpixels + scrollsubpixelsadded))*scrolldirection;
  101. var scrollsubpixelsleft = scrollsubpixels + scrollsubpixelsadded - additionalscrolls;
  102.  
  103. ScrollPixels(target, scrollpixels-fullscrolls-additionalscrolls);
  104. ScrollSubpixels(target, scrollsubpixelsleft);
  105. var scrolldelta = fullscrolls + additionalscrolls;
  106. if (Smoothscroll.Debug>1) {
  107. console.log("scrolldelta: " + scrolldelta);
  108. }
  109. /*
  110. if (target.scrollBy != null) {
  111. target.scrollBy({
  112. top: scrolldelta,
  113. left: 0,
  114. behavior: 'auto'
  115. });
  116.  
  117.  
  118. if (Smoothscroll.Debug>1) {
  119. console.log("target.scrollBy: " + scrolldelta);
  120. }
  121. } else {
  122. */
  123. target.style.scrollBehavior="auto"; // fix for pages with changed scroll-behavior
  124. target.scrollTop = target.scrollTop + scrolldelta;
  125.  
  126.  
  127. if (Smoothscroll.Debug>1) {
  128. console.log("target.scrollTop: " + target.scrollTop);
  129. }
  130.  
  131. target.scrollanimated = true;
  132. RequestAnimationUpdate(function(newrefreshrate) {
  133. AnimateScroll(target, newrefreshrate);
  134. });
  135. } else
  136. {
  137. RequestAnimationUpdate(function(newrefreshrate) {
  138. ScrollPixels(target, 0);
  139. });
  140. target.scrollanimated = false;
  141. }
  142. }
  143.  
  144. function RequestAnimationUpdate(cb) {
  145. var before = performance.now();
  146. window.requestAnimationFrame(() => {
  147. var after = performance.now();
  148. var frametime = after - before;
  149. var calculatedFps = 1000 / Math.max(frametime, 1);
  150.  
  151. var refreshrate = Math.min(Math.max(calculatedFps, Smoothscroll.MinRefreshrate), Smoothscroll.MaxRefreshrate);
  152. //Smoothscroll.Refreshrate = refreshrate;
  153.  
  154. cb(refreshrate);
  155. });
  156. }
  157.  
  158.  
  159. Smoothscroll.Stop = function(target) {
  160. if (target) {
  161. ScrollPixels(target, 0);
  162. }
  163. }
  164. Smoothscroll.Start = function(target, scrollamount) {
  165. if (target) {
  166. var scrolltotal = ScrollPixels(target, scrollamount);
  167. if (!target.scrollanimated) {
  168. AnimateScroll(target, Smoothscroll.Refreshrate);
  169. }
  170.  
  171. //var scrollpixels = ScrollPixels(target);
  172.  
  173. }
  174. }
  175.  
  176. if (typeof module !== 'undefined') {
  177. module.exports = Smoothscroll;
  178. }
  179.  
  180.  
  181. function CanScroll(element, dir) {
  182.  
  183. if (dir<0)
  184. {
  185. return element.scrollTop>0;
  186. }
  187. if (dir>0)
  188. {
  189. if (element==document.body) {
  190. if (element.scrollTop==0) {
  191. element.scrollTop = 3;
  192. if (element.scrollTop==0) {
  193. return false;
  194. }
  195. element.scrollTop = 0;
  196. }
  197. return Math.round(element.clientHeight+element.scrollTop)<(element.offsetHeight);
  198. }
  199. return Math.round(element.clientHeight+element.scrollTop)<(element.scrollHeight);
  200. }
  201. }
  202. function HasScrollbar(element)
  203. {
  204. //TODO: problem with webkit, body not scrollable?
  205. if (element==window || element==document) {
  206. return false;
  207. }
  208. if (element==document.body) {
  209. return window.getComputedStyle(document.body)['overflow-y']!="hidden";
  210. }
  211. //THANK YOU TO: https://tylercipriani.com/blog/2014/07/12/crossbrowser-javascript-scrollbar-detection/
  212. if (element==document.documentElement) {
  213. return window.innerWidth > document.documentElement.clientWidth;
  214. } else {
  215. //return (element.clientWidth-element.clientWidth)>0;
  216. var style = window.getComputedStyle(element);
  217. return style['overflow-y']!="hidden" && style['overflow-y']!="visible";
  218. }
  219.  
  220. }
  221.  
  222. function Scrollable(element, dir)
  223. {
  224. //TODO: problem with webkit, body not scrollable?
  225. if (element==document.body) {
  226. //return false;
  227. }
  228. var scrollablecheck = CanScroll(element, dir);
  229. if (!scrollablecheck) {
  230. if (Smoothscroll.Debug>1) {
  231. console.log("scrollablecheck: " + scrollablecheck);
  232. }
  233. return false;
  234. }
  235. var scrollbarcheck = HasScrollbar(element);
  236. if (!scrollbarcheck) {
  237. if (Smoothscroll.Debug>1) {
  238. console.log("scrollbarcheck: " + scrollbarcheck);
  239. }
  240. return false;
  241. }
  242.  
  243. if (Smoothscroll.Debug>1) {
  244. console.log("scrollablecheck: " + scrollablecheck);
  245. console.log("scrollbarcheck: " + scrollbarcheck);
  246. }
  247. return true;
  248. }
  249. function GetPath(e) {
  250. if (e.path) {
  251. return e.path;
  252. }
  253. if (e.composedPath) {
  254. return e.composedPath();
  255. }
  256. if (Smoothscroll.Debug>1) {
  257. console.log("Smoothscroll: e.path is undefined");
  258. }
  259. return null;
  260. }
  261.  
  262. function GetTarget(e) {
  263. var direction = e.deltaY;
  264. var nodes = GetPath(e);
  265. if (!nodes) {
  266. return null;
  267. }
  268. if (Smoothscroll.Debug>2) {
  269. console.log("nodes: ");
  270. console.log(nodes);
  271. console.log("target: ");
  272. }
  273. for (var i=0; i<(nodes.length); i++) {
  274. var node = nodes[i];
  275. if (Smoothscroll.Debug>2) {
  276. console.log(node);
  277. }
  278. if (Scrollable(node, direction))
  279. {
  280. if (Smoothscroll.Debug>2) {
  281. console.log("true");
  282. }
  283. return node;
  284. }
  285. }
  286. if (Smoothscroll.Debug>1) {
  287. console.log("false");
  288.  
  289. }
  290.  
  291. return null;
  292. }
  293.  
  294. function GetStyleProperty(el, styleprop){
  295. if(window.getComputedStyle){
  296. var heightprop = document.defaultView.getComputedStyle(el, null).getPropertyValue(styleprop);
  297. if (heightprop) {
  298. return parseInt(heightprop);
  299. }
  300. }
  301. else if(el.currentStyle){
  302. var heightprop = el.currentStyle[styleprop.encamel()];
  303. if (heightprop) {
  304. return parseInt(heightprop);
  305. }
  306. }
  307. return null;
  308. }
  309.  
  310. //mouse event scroll handlers
  311. function StopScroll(e) {
  312. var nodes = GetPath(e);
  313. if (!nodes) {
  314. return null;
  315. }
  316.  
  317. for (var i=0; i<(nodes.length); i++) {
  318. var node = nodes[i];
  319. Smoothscroll.Stop(node);
  320. }
  321. }
  322. function StartScroll(e, target) {
  323.  
  324. if (e.defaultPrevented)
  325. {
  326. return true;
  327. }
  328. else
  329. {
  330. var delta = e.deltaY;
  331. if (Smoothscroll.Debug) {
  332. console.log("e: ", e);
  333. }
  334.  
  335. if (e.deltaMode && e.deltaMode==1) {
  336. var line = GetStyleProperty(target, 'line-height');
  337. if (Smoothscroll.Debug) {
  338. console.log("line: " + line);
  339. }
  340. if (line && line>0) {
  341. delta = e.deltaY * line;
  342. }
  343. }
  344.  
  345. if (e.deltaMode && e.deltaMode==2) {
  346. var page = target.clientHeight;
  347. if (Smoothscroll.Debug) {
  348. console.log("page: " + page);
  349. }
  350. if (page && page>0) {
  351. delta = e.deltaY * page;
  352. }
  353. }
  354.  
  355. var scrollpixels = ScrollPixels(target);
  356.  
  357. var accelerationratio = Math.sqrt(Math.abs(scrollpixels/delta*Smoothscroll.Acceleration));
  358.  
  359. var acceleration = Math.round(delta*accelerationratio);
  360.  
  361. var totalscroll = scrollpixels + delta + acceleration;
  362. if (Smoothscroll.Debug) {
  363. console.log("scrollpixels: " + scrollpixels);
  364. console.log("delta: " + delta);
  365. console.log("acceleration: " + acceleration);
  366. console.log("totalscroll: " + totalscroll);
  367. }
  368.  
  369. Smoothscroll.Start(target, totalscroll);
  370.  
  371. e.preventDefault();
  372. }
  373. }
  374.  
  375. //mouse event call handlers
  376. function WheelEvent(e) {
  377. var target = GetTarget(e);
  378.  
  379. if (target) {
  380. StartScroll(e, target);
  381. }
  382. }
  383. function ClickEvent(e) {
  384. StopScroll(e);
  385. }
  386.  
  387. /*
  388. function GetFrametime(cb) {
  389. var before = performance.now();
  390. window.requestAnimationFrame(() => {
  391. var after = performance.now();
  392. var diff = after - before;
  393.  
  394. if (cb) {
  395. cb(diff);
  396. }
  397. });
  398. }
  399.  
  400. function UpdateRefreshrateInternal(cb) {
  401. GetFrametime((frametime) => {
  402. var calculatedFps = 1000 / Math.max(frametime, 1);
  403. Smoothscroll.Refreshrate = Math.min(Math.max(calculatedFps, Smoothscroll.MinRefreshrate), Smoothscroll.MaxRefreshrate);
  404. if (Smoothscroll.Debug > 3) {
  405. console.log("Smoothscroll.Refreshrate: " + Smoothscroll.Refreshrate);
  406. }
  407. if (cb) {
  408. cb();
  409. }
  410. });
  411. }
  412.  
  413. var updateRefreshrateLoopTimer = null;
  414. function UpdateRefreshrate() {
  415. if (updateRefreshrateLoopTimer) {
  416. clearTimeout(updateRefreshrateLoopTimer);
  417. }
  418. UpdateRefreshrateInternal(()=>{
  419. updateRefreshrateLoopTimer = setTimeout(()=>{
  420. UpdateRefreshrate();
  421. },1000/Smoothscroll.BaseRefreshrate);
  422. });
  423. };
  424. */
  425.  
  426. //init function
  427. function Init()
  428. {
  429. if (window.top != window.self) {
  430. //console.log("Smoothscroll: ignoring iframe");
  431. return null;
  432. }
  433. if (window.Smoothscroll && window.Smoothscroll.Loaded) {
  434. //console.log("Smoothscroll: already loaded");
  435. return null;
  436. }
  437.  
  438. //Smoothscroll.Refreshrate = Smoothscroll.BaseRefreshrate;
  439.  
  440. if (!window.requestAnimationFrame) {
  441. window.requestAnimationFrame =
  442. window.mozRequestAnimationFrame ||
  443. window.webkitRequestAnimationFrame;
  444. }
  445.  
  446. document.documentElement.addEventListener("wheel", function(e){
  447. WheelEvent(e);
  448. if (Smoothscroll.Debug>0) {
  449. console.log(e);
  450. }
  451. },{ passive: false });
  452.  
  453. document.documentElement.addEventListener("mousedown", function(e){
  454. ClickEvent(e);
  455. if (Smoothscroll.Debug>0) {
  456. console.log(e);
  457. }
  458. });
  459. window.Smoothscroll = Smoothscroll;
  460. window.Smoothscroll.Loaded = true;
  461. //window.requestAnimationFrame(Fps);
  462.  
  463. //UpdateRefreshrate();
  464.  
  465. //UpdateRefreshrate();
  466. console.log("Smoothscroll: loaded");
  467. }
  468. Init();