Page Scroll Marker

Adds translucent bars to the top and bottom of the viewport, which indicates the previous viewport position when the page is scrolled. This tool will prevents you from getting lost when you press the Page Up/Down keys.

目前为 2017-07-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Page Scroll Marker
  3. // @namespace http://userscripts.org/users/mstm
  4. // @description Adds translucent bars to the top and bottom of the viewport, which indicates the previous viewport position when the page is scrolled. This tool will prevents you from getting lost when you press the Page Up/Down keys.
  5. // @version 0.9
  6. // @include *
  7. // @grant GM_addStyle
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. if (document.designMode == 'on' || document.body instanceof HTMLFrameSetElement || document.URL.indexOf(location.protocol) != 0) return;
  14.  
  15. const generalName = GM_info.script.name;
  16. const containerID = GM_info.script.namespace.concat(generalName).replace(/\W/g, '');
  17.  
  18. GM_addStyle('.' + containerID + ' { display: none !important; background: transparent !important; border: none !important; } .' + containerID + '.active { display: block !important; } .' + containerID + ' div { position: absolute !important; width: 100% !important; height: 8px !important; background: #808080 !important; z-index: 65535 !important; opacity: 0.25 !important; font: 8px/1 sans-serif !important; color: #FF0000 !important; text-align: center !important; border: none !important; } .' + containerID + ' div:after { content: attr(title); }');
  19.  
  20. var ScrollNode = function (target) {
  21. this.initialize(target);
  22. };
  23.  
  24. ScrollNode.prototype = {
  25. update: function () {
  26. clearTimeout(this.tid);
  27. this.lines.className = this.lines.className.replace(/\bactive\b/g, '').replace(/\s+/, ' ').replace(/^\s?(.*?)\s?$/, '$1');
  28. this.upper.style.top = + this.owner.scrollTop + 'px';
  29. this.lower.style.bottom = - this.owner.scrollTop + 'px';
  30. },
  31.  
  32. scroll: function () {
  33. clearTimeout(this.tid);
  34. this.upper.style.left = this.lower.style.left = this.owner.scrollLeft + 'px';
  35. this.lines.className = this.lines.className.split(' ').concat('active').join(' ');
  36. this.tid = setTimeout((function (o, f) {
  37. return function () {
  38. f.call(o);
  39. };
  40. })(this, this.update), 1000);
  41.  
  42. if (GM_getValue('wrap_bars', true)) {
  43. if (this.lower.offsetTop + this.lower.offsetHeight < this.owner.scrollTop) {
  44. this.upper.style.top = parseInt(this.upper.style.top) + this.owner.clientHeight + 'px';
  45. this.lower.style.bottom = parseInt(this.lower.style.bottom) - this.owner.clientHeight + 'px';
  46. }
  47.  
  48. if (this.upper.offsetTop - this.owner.clientHeight > this.owner.scrollTop) {
  49. this.upper.style.top = parseInt(this.upper.style.top) - this.owner.clientHeight + 'px';
  50. this.lower.style.bottom = parseInt(this.lower.style.bottom) + this.owner.clientHeight + 'px';
  51. }
  52. }
  53. },
  54.  
  55. initialize: function (target) {
  56. this.isTop = target.nodeName == document.nodeName;
  57. this.owner = this.isTop ? target.body : target;
  58. this.lines = this.owner.appendChild(document.createElement('DIV'));
  59. this.lines.className = containerID;
  60. this.lines.title = generalName;
  61. this.upper = this.lines.appendChild(document.createElement('DIV'));
  62. this.upper.title = '\u25bc';
  63. this.lower = this.lines.appendChild(document.createElement('DIV'));
  64. this.lower.title = '\u25b2';
  65.  
  66. if (this.isTop) {
  67. if (document.compatMode != 'BackCompat') {
  68. this.owner = document.documentElement;
  69. }
  70. }
  71. else {
  72. if (document.defaultView.getComputedStyle(this.owner, null).getPropertyValue('position') == 'static') {
  73. this.owner.style.position = 'relative';
  74. }
  75.  
  76. this.lines.addEventListener('DOMNodeRemoved', function (e) {
  77. ScrollNode.put(e.relatedNode);
  78. }, false);
  79.  
  80. this.lines.addEventListener('DOMNodeRemoved', function (e) {
  81. e.stopPropagation();
  82. }, true);
  83. }
  84.  
  85. this.update();
  86. }
  87. };
  88.  
  89. ScrollNode.instances = {};
  90. ScrollNode.remaining = 10;
  91.  
  92. ScrollNode.put = function (target) {
  93. if (--this.remaining < 0) {
  94. return this.get(document);
  95. }
  96.  
  97. var s = new ScrollNode(target);
  98. this.instances[s.isTop ? target.nodeName : (target.id = target.id || '__id__' + (this.uid = (this.uid || 0) + 1))] = s;
  99. return s;
  100. };
  101.  
  102. ScrollNode.get = function (target) {
  103. return this.instances[target.nodeName] || this.instances[target.id];
  104. };
  105.  
  106. ScrollNode.put(document);
  107.  
  108. document.addEventListener('scroll', function (e) {
  109. if (GM_getValue('recursive', true) && !('value' in e.target)) {
  110. (ScrollNode.get(e.target) || ScrollNode.put(e.target)).scroll();
  111. }
  112. }, true);
  113.  
  114. document.addEventListener('resize', function (e) {
  115. for (var e in ScrollNode.instances) {
  116. ScrollNode.instances[e].update();
  117. }
  118. }, true);
  119.  
  120. GM_setValue('wrap_bars', GM_getValue('wrap_bars', true));
  121. GM_setValue('recursive', GM_getValue('recursive', true));
  122. })()
  123.