Select like Opera

An userscript to provide Opera(old) like scrolling behavior.

目前为 2014-12-23 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Select like Opera
  3. // @description An userscript to provide Opera(old) like scrolling behavior.
  4. // @namespace eight04.blogspot.com
  5. // @include http*
  6. // @version 1.0.0
  7. // @require https://greasyfork.org/scripts/1884-gm-config/code/GM_config.js?version=4836
  8. // @require https://greasyfork.org/scripts/7108-bezier-easing/code/bezier-easing.js?version=29098
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_registerMenuCommand
  12. // ==/UserScript==
  13.  
  14. (function(){
  15.  
  16. "use strict";
  17.  
  18. var config = {
  19. scrollHorizontallyWithScrollbar: true,
  20. scrollOverflowHidden: false,
  21. alwaysUse: false,
  22. scrollDelay: 400,
  23. scrollOffset: 120
  24. };
  25.  
  26. initConfig(config);
  27.  
  28. /**
  29. Register event
  30. */
  31. window.addEventListener("wheel", function(e){
  32. var q = getScrollInfo(e.target, e);
  33.  
  34. if (q && (q.use || config.alwaysUse)) {
  35. e.preventDefault();
  36. scroll(q.element, q.offsetX, q.offsetY);
  37. }
  38. });
  39.  
  40. /**
  41. Main logic
  42. */
  43. function getInfo(element, e) {
  44. var rect, border;
  45.  
  46. if (element != document.documentElement) {
  47.  
  48. rect = element.getBoundingClientRect();
  49. border = getBorder(element);
  50.  
  51. return {
  52. element: element,
  53. onScrollbarX: e.clientY >= rect.top + border.top + element.clientHeight && e.clientY <= rect.bottom - border.bottom,
  54. scrollableX: element.scrollWidth > element.clientWidth,
  55. scrollableY: element.scrollHeight > element.clientHeight,
  56. scrollerX: element.offsetHeight - border.top - border.bottom > element.clientHeight,
  57. scrollerY: element.offsetWidth - border.left - border.right > element.clientWidth
  58. };
  59.  
  60. } else {
  61.  
  62. return {
  63. element: element,
  64. onScrollBarX: e.clientY >= element.clientHeight && e.clientY <= window.innerHeight,
  65. scrollableX: element.scrollWidth > element.clientWidth,
  66. scrollableY: element.scrollHeight > element.clientHeight,
  67. scrollerX: window.innerHeight - element.clientHeight,
  68. scrollerY: window.innerWidth - element.clientWidth
  69. };
  70. }
  71. }
  72.  
  73. function getScrollInfo(element, e) {
  74. var q;
  75.  
  76. // Get scrollable parent
  77. while (element) {
  78.  
  79. q = getInfo(element, e);
  80.  
  81. if (e.deltaY && (q.onScrollbarX || useHorizontalScroll(q)) && scrollable(element, e.deltaY, 0)) {
  82. // Horizontal scroll
  83. q.offsetX = getOffset(e.deltaY);
  84. q.offsetY = 0;
  85. q.use = true;
  86. return q;
  87. }
  88.  
  89. if ((e.deltaX && q.scrollableX || e.deltaY && q.scrollableY) && scrollable(element, e.deltaX, e.deltaY)) {
  90. if (e.deltaX && q.scrollerX || e.deltaY && q.scrollerY) {
  91. // Usual cases
  92. q.offsetX = getOffset(e.deltaX);
  93. q.offsetY = getOffset(e.deltaY);
  94. return q;
  95. }
  96. if (config.scrollOverflowHidden && (e.deltaX && !q.scrollerX || e.deltaY && !q.scrollerY)) {
  97. // Scroll hidden
  98. q.offsetX = getOffset(e.deltaX);
  99. q.offsetY = getOffset(e.deltaY);
  100. q.use = true;
  101. return q;
  102. }
  103. }
  104.  
  105. element = element.parentNode;
  106. if (element == document) {
  107. return null;
  108. }
  109. }
  110.  
  111. return null;
  112. }
  113.  
  114. /**
  115. Scroll function. Should I put them into seperate library?
  116. Thanks to https://github.com/galambalazs/smoothscroll
  117. */
  118. var animate = null;
  119. var que = [];
  120. function scroll(element, x, y) {
  121. var elapsed = config.scrollDelay;
  122.  
  123. que.push({
  124. offsetX: x,
  125. offsetY: y,
  126. lastX: 0,
  127. lastY: 0,
  128. element: element,
  129. timeStart: null
  130. });
  131.  
  132. if (animate != null) {
  133. return;
  134. }
  135.  
  136. function animation(timestamp){
  137. var i, j, len, q, time, offsetX, offsetY, process, swap;
  138.  
  139. swap = [];
  140.  
  141. for (i = 0, j = 0, len = que.length; i < len; i++) {
  142. q = que[i];
  143. if (q.timeStart == null) {
  144. q.timeStart = timestamp;
  145. }
  146. if (timestamp - q.timeStart >= elapsed || !scrollable(q.element, q.offsetX, q.offsetY)) {
  147. scrollBy(q.element, q.offsetX - q.lastX, q.offsetY - q.lastY);
  148. } else {
  149. time = (timestamp - q.timeStart) / elapsed;
  150. process = ease(time);
  151. offsetX = Math.floor(q.offsetX * process);
  152. offsetY = Math.floor(q.offsetY * process);
  153.  
  154. scrollBy(q.element, offsetX - q.lastX, offsetY - q.lastY);
  155.  
  156. q.lastX = offsetX;
  157. q.lastY = offsetY;
  158. swap[j++] = q;
  159. }
  160. }
  161.  
  162. que = swap;
  163. if (!que.length) {
  164. animate = null;
  165. return;
  166. }
  167.  
  168. animate = requestAnimationFrame(animation);
  169. }
  170. animate = requestAnimationFrame(animation);
  171. }
  172.  
  173. /**
  174. Helpers
  175. */
  176. function useHorizontalScroll(q) {
  177. if (!config.scrollHorizontallyWithScrollbar) {
  178. return false;
  179. }
  180. if (config.scrollOverflowHidden && !q.scrollerX && !q.scrollerY) {
  181. return q.scrollableX && !q.scrollableY;
  182. }
  183. return q.scrollerX && (!q.scrollerY || !q.scrollableY);
  184. }
  185.  
  186. function getOffset(delta) {
  187. var direction = 0;
  188. if (delta > 0) {
  189. direction = 1;
  190. } else if (delta < 0) {
  191. direction = -1;
  192. }
  193. return direction * config.scrollOffset;
  194. }
  195.  
  196. function ease(t){
  197. return BezierEasing.css.ease(t);
  198. }
  199.  
  200. function getBorder(element){
  201. var css = getComputedStyle(element);
  202.  
  203. return {
  204. top: parseInt(css.getPropertyValue("border-top-width"), 10),
  205. right: parseInt(css.getPropertyValue("border-right-width"), 10),
  206. bottom: parseInt(css.getPropertyValue("border-bottom-width"), 10),
  207. left: parseInt(css.getPropertyValue("border-left-width"), 10)
  208. };
  209. }
  210.  
  211. function scrollable(element, offsetX, offsetY) {
  212. var top, left;
  213. if (element == document.documentElement) {
  214. top = window.scrollY;
  215. left = window.scrollX;
  216. } else {
  217. top = element.scrollTop;
  218. left = element.scrollLeft;
  219. }
  220.  
  221. if (top == 0 && offsetY < 0) {
  222. return false;
  223. }
  224. if (left == 0 && offsetX < 0) {
  225. return false;
  226. }
  227. if (top + element.clientHeight >= element.scrollHeight && offsetY > 0) {
  228. return false;
  229. }
  230. if (left + element.clientWidth >= element.scrollWidth && offsetX > 0) {
  231. return false;
  232. }
  233. return true;
  234. }
  235.  
  236. function scrollBy(element, x, y) {
  237. if (element != document.documentElement) {
  238. element.scrollLeft += x;
  239. element.scrollTop += y;
  240. } else {
  241. window.scrollBy(x, y);
  242. }
  243. }
  244.  
  245. function initConfig(config) {
  246. GM_config.init(
  247. "Scroll like Opera",
  248. {
  249. scrollHorizontallyWithScrollbar: {
  250. label: "Scroll horizontally if only horizontal scrollbar presented.",
  251. type: "checkbox",
  252. default: true
  253. },
  254. scrollOverflowHidden: {
  255. label: "Make overflow:hidden element scrollable.",
  256. type: "checkbox",
  257. default: false
  258. },
  259. alwaysUse: {
  260. label: "Always use script's scrolling handler. Enable this if you want to use the script's smooth scrolling on chrome.",
  261. type: "checkbox",
  262. default: false
  263. },
  264. scrollDelay: {
  265. label: "Smooth scrolling dealy.",
  266. type: "text",
  267. default: 400
  268. },
  269. scrollOffset: {
  270. label: "Scrolling offset.",
  271. type: "text",
  272. default: 120
  273. }
  274. }
  275. );
  276.  
  277. config.scrollHorizontallyWithScrollbar = GM_config.get("scrollHorizontallyWithScrollbar");
  278. config.scrollOverflowHidden = GM_config.get("scrollOverflowHidden");
  279. config.alwaysUse = GM_config.get("alwaysUse");
  280. config.scrollDelay = +GM_config.get("scrollDelay");
  281. config.scrollOffset = +GM_config.get("scrollOffset");
  282.  
  283. GM_registerMenuCommand("Linkify Plus Plus - Configure", function(){
  284. GM_config.open();
  285. });
  286. }
  287. })();