Greasy Fork 还支持 简体中文。

Smoothscroll

Smooth scrolling on pages using javascript

目前為 2024-01-18 提交的版本,檢視 最新版本

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