Greasy Fork is available in English.

Scroll like Opera

Based on Linkify Plus. Turn plain text URLs into links.

  1. // ==UserScript==
  2. // @name Scroll like Opera
  3. // @version 2.1.1
  4. // @description Based on Linkify Plus. Turn plain text URLs into links.
  5. // @license MIT
  6. // @author eight04 <eight04@gmail.com>
  7. // @homepageURL https://github.com/eight04/scroll-like-opera
  8. // @supportURL https://github.com/eight04/scroll-like-opera/issues
  9. // @namespace eight04.blogspot.com
  10. // @grant GM.getValue
  11. // @grant GM.setValue
  12. // @grant GM.deleteValue
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_deleteValue
  17. // @grant GM_addValueChangeListener
  18. // @compatible firefox Tampermonkey latest
  19. // @compatible chrome Tampermonkey latest
  20. // @require https://greasyfork.org/scripts/7212-gm-config-eight-s-version/code/GM_config%20(eight's%20version).js?version=29833
  21. // @require https://greasyfork.org/scripts/7108-bezier-easing/code/bezier-easing.js?version=29098
  22. // @include *
  23. // ==/UserScript==
  24.  
  25. /* eslint-env browser, greasemonkey */
  26. /* global GM_config BezierEasing */
  27.  
  28. var config;
  29.  
  30. GM_config.init(
  31. "Scroll like Opera",
  32. {
  33. useWhenOnScrollbar: {
  34. label: "Scroll horizontally if cursor hover on horizontal scrollbar.",
  35. type: "checkbox",
  36. default: true
  37. },
  38. useWhenOneScrollbar: {
  39. label: "Scroll horizontally if there is only horizontal scrollbar presented.",
  40. type: "checkbox",
  41. default: true
  42. },
  43. useAlways: {
  44. label: "Always use script's scrolling handler. Enable this if you want to use the script's smooth scrolling on chrome.",
  45. type: "checkbox",
  46. default: false
  47. },
  48. scrollDelay: {
  49. label: "Smooth scrolling delay.",
  50. type: "text",
  51. default: 400
  52. },
  53. scrollOffset: {
  54. label: "Scrolling offset.",
  55. type: "text",
  56. default: 120
  57. },
  58. continueScrollingTimeout: {
  59. label: "Scrolled target changing delay.",
  60. type: "number",
  61. default: 400
  62. }
  63. }
  64. );
  65.  
  66. config = GM_config.get();
  67.  
  68. GM_registerMenuCommand("Scroll like Opera - Configure", function(){
  69. GM_config.open();
  70. });
  71.  
  72. GM_config.onclose = function(){
  73. config = GM_config.get();
  74. };
  75.  
  76. /**
  77. Cache current scrolling target
  78. */
  79. var cache = {
  80. timeout: null,
  81. target: null,
  82. cache: function(element) {
  83. cache.target = element;
  84. cache.delay();
  85. },
  86. reset: function() {
  87. clearTimeout(cache.timeout);
  88. cache.timeout = null;
  89. cache.target = null;
  90. },
  91. delay: function() {
  92. clearTimeout(cache.timeout);
  93. cache.timeout = setTimeout(cache.reset, config.continueScrollingTimeout);
  94. }
  95. };
  96.  
  97. /**
  98. Register event
  99. */
  100. window.addEventListener("wheel", function(e){
  101. var q;
  102.  
  103. if (cache.target) {
  104. q = getScrollInfo(cache.target, e, true);
  105.  
  106. // Scrolled to edge
  107. if (!q) {
  108. e.preventDefault();
  109. return;
  110. }
  111.  
  112. cache.delay();
  113. } else {
  114. q = getScrollInfo(e.target, e);
  115.  
  116. // Can't find scrollable element
  117. if (!q) {
  118. return;
  119. }
  120.  
  121. cache.cache(q.element);
  122. }
  123.  
  124. if (q.use || config.useAlways) {
  125. e.preventDefault();
  126. scrollElement(q.element, q.offsetX, q.offsetY);
  127. }
  128. }, {passive: false});
  129.  
  130. window.addEventListener("mousemove", function(){
  131. cache.reset();
  132. });
  133.  
  134. /**
  135. Main logic
  136. */
  137. function getInfo(element, e) {
  138. var rect, css;
  139.  
  140. if (element == document.documentElement) {
  141. return {
  142. element: element,
  143. onScrollbarX: e.clientY >= element.clientHeight && e.clientY <= window.innerHeight,
  144. scrollableX: element.scrollWidth > element.clientWidth,
  145. scrollableY: element.scrollHeight > element.clientHeight
  146. };
  147. } else if (element == document.body) {
  148. return {
  149. element: element,
  150. onScrollbarX: false,
  151. scrollableX: false,
  152. scrollableY: false
  153. };
  154. } else {
  155. rect = element.getBoundingClientRect();
  156. css = getCss(element);
  157.  
  158. return {
  159. element: element,
  160. onScrollbarX: element.clientHeight && e.clientY >= rect.top + css.borderTop + element.clientHeight && e.clientY <= rect.bottom - css.borderBottom,
  161. scrollableX: element.clientWidth && element.scrollWidth > element.clientWidth && css.overflowX != "visible" && css.overflowX != "hidden",
  162. scrollableY: element.clientHeight && element.scrollHeight > element.clientHeight && css.overflowY != "visible" && css.overflowY != "hidden"
  163. };
  164. }
  165. }
  166.  
  167. function getScrollInfo(element, e, noParent) {
  168. var q;
  169.  
  170. // Get scrollable parent
  171. while (element) {
  172.  
  173. q = getInfo(element, e);
  174.  
  175. if (e.deltaY && (q.onScrollbarX || useHorizontalScroll(q)) && scrollable(element, e.deltaY, 0)) {
  176. // Horizontal scroll
  177. q.offsetX = getOffset(e.deltaY);
  178. q.offsetY = 0;
  179. q.use = true;
  180. return q;
  181. }
  182.  
  183. if ((e.deltaX && q.scrollableX || e.deltaY && q.scrollableY) && scrollable(element, e.deltaX, e.deltaY)) {
  184. q.offsetX = getOffset(e.deltaX);
  185. q.offsetY = getOffset(e.deltaY);
  186. return q;
  187. }
  188.  
  189. if (noParent) {
  190. return null;
  191. }
  192.  
  193. element = element.parentNode;
  194. if (element == document) {
  195. return null;
  196. }
  197. }
  198.  
  199. return null;
  200. }
  201.  
  202. /**
  203. Scroll function. Should I put them into seperate library?
  204. Thanks to https://github.com/galambalazs/smoothscroll
  205. */
  206. var animate = null;
  207. var que = [];
  208. function scrollElement(element, x, y) {
  209. var elapsed = config.scrollDelay;
  210.  
  211. que.push({
  212. offsetX: x,
  213. offsetY: y,
  214. lastX: 0,
  215. lastY: 0,
  216. element: element,
  217. timeStart: null
  218. });
  219.  
  220. if (animate != null) {
  221. return;
  222. }
  223.  
  224. function animation(timestamp){
  225. var i, j, len, q, time, offsetX, offsetY, process, swap;
  226.  
  227. swap = [];
  228.  
  229. for (i = 0, j = 0, len = que.length; i < len; i++) {
  230. q = que[i];
  231. if (q.timeStart == null) {
  232. q.timeStart = timestamp;
  233. }
  234. if (timestamp - q.timeStart >= elapsed || !scrollable(q.element, q.offsetX, q.offsetY)) {
  235. scrollBy(q.element, q.offsetX - q.lastX, q.offsetY - q.lastY);
  236. } else {
  237. time = (timestamp - q.timeStart) / elapsed;
  238. process = ease(time);
  239. offsetX = Math.floor(q.offsetX * process);
  240. offsetY = Math.floor(q.offsetY * process);
  241.  
  242. scrollBy(q.element, offsetX - q.lastX, offsetY - q.lastY);
  243.  
  244. q.lastX = offsetX;
  245. q.lastY = offsetY;
  246. swap[j++] = q;
  247. }
  248. }
  249.  
  250. que = swap;
  251. if (!que.length) {
  252. animate = null;
  253. return;
  254. }
  255.  
  256. animate = requestAnimationFrame(animation);
  257. }
  258. animate = requestAnimationFrame(animation);
  259.  
  260. function scrollBy(element, x, y) {
  261. if (element != document.documentElement) {
  262. element.scrollLeft += x;
  263. element.scrollTop += y;
  264. } else {
  265. window.scrollBy(x, y);
  266. }
  267. }
  268. }
  269.  
  270. /**
  271. Helpers
  272. */
  273. function useHorizontalScroll(q) {
  274. return config.useWhenOneScrollbar && q.scrollableX && !q.scrollableY;
  275. }
  276.  
  277. function getOffset(delta) {
  278. var direction = 0;
  279. if (delta > 0) {
  280. direction = 1;
  281. } else if (delta < 0) {
  282. direction = -1;
  283. }
  284. return direction * config.scrollOffset;
  285. }
  286.  
  287. function ease(t){
  288. return BezierEasing.css.ease(t);
  289. }
  290.  
  291. function getCss(element){
  292. var css = getComputedStyle(element);
  293.  
  294. return {
  295. borderTop: parseInt(css.getPropertyValue("border-top-width"), 10),
  296. borderRight: parseInt(css.getPropertyValue("border-right-width"), 10),
  297. borderBottom: parseInt(css.getPropertyValue("border-bottom-width"), 10),
  298. borderLeft: parseInt(css.getPropertyValue("border-left-width"), 10),
  299. overflowX: css.getPropertyValue("overflow-x"),
  300. overflowY: css.getPropertyValue("overflow-y")
  301. };
  302. }
  303.  
  304. function scrollable(element, offsetX, offsetY) {
  305. var top, left;
  306. if (element == document.documentElement) {
  307. top = window.scrollY;
  308. left = window.scrollX;
  309. } else {
  310. top = element.scrollTop;
  311. left = element.scrollLeft;
  312. }
  313.  
  314. if (top == 0 && offsetY < 0) {
  315. return false;
  316. }
  317. if (left == 0 && offsetX < 0) {
  318. return false;
  319. }
  320. if (top + element.clientHeight >= element.scrollHeight && offsetY > 0) {
  321. return false;
  322. }
  323. if (left + element.clientWidth >= element.scrollWidth && offsetX > 0) {
  324. return false;
  325. }
  326. return true;
  327. }
  328.