Greasy Fork is available in English.

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.

  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.2
  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. var generalName = GM_info.script.name;
  16. var 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 sf in ScrollNode.instances) {
  116. ScrollNode.instances[sf].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.