• 使用GreaseMonkey给页面增加”返回顶部”功能


      在网上浏览发现很多页面都提供了”返回顶部”功能,就是当你向下滚动页面时,会在页面右下方或者其他某个位置出现一个按钮,点击这个按钮,页面会自动回到顶部。

      我很喜欢这个功能,但并非所有页面都提供了这样的按钮,所以我很自然的就想到用GreaseMonkey来实现,其实代码在去年的时候就已经写完, 但我当时对于JavaScript的了解实在有限,在重写脚本的过程中,不断发现过去从未关注过的问题,这样的过程对我来说很有意义,因为能够发现自己是真的进步了,下面就将整个过程分析一下,核心代码仍然十分简单。

      这一段是去年写的,其中省略的那部分是一张图片的base64代码:

    View Code
      1 var imgDiv = document.getElementById('#toTheTop');   2    3 function createDiv() {   4    5   imgDiv = document.createElement('div');   6    7   imgDiv.id = '#toTheTop';   8    9   imgDiv.style.position = 'fixed';  10   11   imgDiv.style.display = 'none';  12   13   imgDiv.style.left = '90%';  14   15   imgDiv.style.top = '90%';  16   17   imgDiv.innerHTML = "<img title='Go To The Top!' style='z-index:999999;   cursor:pointer; opacity:0.7;' src='data:image/png;base64,省略的图片代码' >";  18   19   document.body.appendChild(imgDiv);  20   21   imgDiv.addEventListener('click', function() {  22   23     window.scrollTo(0, 0);  24   25   } ,false);  26   27 }  28   29    30   31 window.addEventListener('scroll', function() {  32   33   if(pageHeight() - scrollY() - windowHeight() <= parseInt(pageHeight()) / 2) {  34   35     if(!imgDiv) {  36   37       createDiv();  38   39     }  40   41     imgDiv.style.display = 'block';  42   43   } else {  44   45     if(!imgDiv) {  46   47       createDiv();  48   49     }  50   51     imgDiv.style.display = 'none';  52   53   }  54   55 }, false);  56   57    58   59 function pageHeight() {  60   61   return document.body.scrollHeight;  62   63 }  64   65 function scrollY() {  66   67   //ie6 strict模式里的快捷方式 68   69   var de = document.documentElement;  70   71   //如果浏览器的pageYOffset可用,则使用之 72   73   return self.pageYOffset ||  74   75     //否则,尝试取得根节点的垂直滚动量 76   77     ( de && de.scrollTop ) ||  78   79     //最后,尝试取得body元素的垂直滚动量 80   81     document.body.scrollTop;  82   83 }  84   85   //取得视口高度 86   87 function windowHeight() {  88   89   //ie6 strict模式里的快捷方式 90   91   var de = document.documentElement;  92   93   //如果浏览器的innerHeight可用,则使用它 94   95   return self.innerHeight ||  96   97     //否则,尝试获得根节点的高度 98   99     ( de && de.clientHeight ) || 100  101     //最后,尝试获得body元素的高度102  103     document.body.clientHeight; 104  105 }

      这段代码可以工作,但问题不少:

        对div样式的设置十分麻烦,好的做法是将样式写进css中,这样阅读起来更紧凑,代码也会相对短一些。

        window的scroll事件绑定的方法中,像判断imgDiv对象是否存在的代码被写了两次,而且很明显的,imgDiv是全局变量。

        scrollY与windowHeight两个函数都是我从John Resig写的《精通JavaScript》中抄下来的,其实既然我已经使用了GreaseMonkey,那么就表明浏览器肯定是 Firefox(chrome也开始支持GreaseMonkey的脚本了),那么一些判断就没有必要,当然,这个问题可以忽略。

        我在测试过程中还发现一个性能问题,就是scroll事件中第一行判断的代码,在每一次页面滚动时,这个表达式都会被计算一次,可实际上,pageHeight这个函数的返回值代表的是当前页面的真实高度,计算一次之后就不会变化,多次计算明显是一种性能上的浪费。

     

      开始解决上面出现的问题吧,首先就是全局变量的问题,良好的编码习惯是尽量少出现全局变量,最简单的方式就是将代码放进一个匿名函数中,例如:

    (function() {     //要执行的代码   }())

     

      接下来是关于代码组织的问题,原始代码写了4个函数,都是在scroll事件绑定的函数中调用,大部分的逻辑判断也写在这里面,显得很凌乱。

      首先来分析下,当scroll事件发生时,你想做什么?

      我的想法是,当滚动条离开顶部向下滚动时,我需要显示一个按钮,当滚动条回到顶部时,我需要这个按钮隐藏。我可以定义一个scroll对象,在对象内定义show与hide两个函数,用来控制按钮的显示与隐藏,我还需要做一个判断,到底什么时候才让按钮显示?      看看原来代码是怎么写的,pageHeight() – scrollY() – windowHeight() <= parseInt(pageHeight()) / 2,我确实不知道当时怎么想的……为什么要这么写?实际上这里面只有scrollY()是必要的,就是滚动条与顶部的距离,当这个距离大于0时,表明开始滚动,这个时候就可以让按钮显示,如果这个值为0,按钮隐藏,就这么简单,根本不需要做那些运算。      好吧,我把scrollY()这个函数也加进scroll对象中,把它的名字改成getScrollY(),让它的意思更明显一些,这样scroll事件中的代码就可以写成:

    1 if(scroll.getScrollY() > 0) { 2     scroll.show(); 3   } else {
    4 scroll.hide();
    5 }

     

      看起来还不错,但是稍微有些长,if else这样的判断可以用另外的运算符代替,那是什么呢?答案是三目运算符!

    (scroll.getScrollY() > 0) ? scroll.show() : scroll.hide();

      暂且不管这算不算是良好的编码习惯,但它确实很短,也比原来的代码cool一些:) 接下来再看滚动的代码,只有一行:window.scrollTo(0, 0);两个参数表示需要滚动的坐标,这段代码没有问题,但点完按钮后,页面立刻就回到了顶部,太直接了,如果能有一些动画效果是不是更好呢?

      做法就是周期性的调用window.scrollTo方法,将纵坐标作为变量传进去,这样就可以模拟滚动的动画效果了。我们已经能够使用getScrollY()获得滚动条和顶部的距离,将这个距离作为变量传递进去,并不断将其减少,直到变成0,这就是接下来要做的事情:

     

      我在scroll对象中定义了一个属性_scrollY用于保存距离值,

    this._scrollY -= 100; window.scrollTo(0, this._scrollY);

      这就是主要代码,接下来的问题是如何周期性的调用它,在这里使用了setTimeout:

    if(this._scrollY > 0) {
    setTimeout(回调函数, 10);
    }

     

      当10毫秒之后就会调用回调函数,但是”回调函数”究竟是什么?

      实际上我在scroll对象中定义了一个scrollToTop函数,上面关于滚动的代码就写在里面,也就是说这里的回调函数实际上就是scroll.scrollToTop,但这么写是有问题的,问题就出现在this上面。

      如果直接把scroll.scrollToTop传进去,那么当运行时,scrollToTop中的this并非是scroll对象,而是全局对象,这样 this._scrollY的值就是undefined,但是,这并不影响滚动,因为我测试发现,最后的情况和直接调用 window.scrollTo(0, 0)的效果是一样。

      解决这个问题的关键,就是要让scrollToTop中的this绑定到正确的对象即scroll上去,这里我使用了一个叫做bind的函数,是从上面提到的书中提供的:

    function bind(context, name) {
    return function() {
    return context[name].apply(context, arguments);
    }
    }

      apply方法可以将函数绑定到指定的对象上,所以最后传入的回调函数是:

    bind(scroll, ‘scrollToTop’);

     

      按理说代码写的差不多了,但我后来又发现,当调用window.scrollTo方法时,scroll事件也会被触发,也就是说绑定到事件中的代码还是会被执行,原来写的代码只会调用一次window.scrollTo,所以不用考虑这种情况,但现在会周期的调用这个方法,scroll事件也会周期的被触发,为了性能上的考虑,我在scroll对象中增加了一个布尔值,用于判断当前的滚动事件是由用户触发,还是由scrollToTop这个函数触发。

      在脚本写完后不久,当我浏览一个博客时,我发现点击这个返回顶部按钮,页面竟然跳转到了博客的首页,后来检查发现是因为a标签的问题,原因大概是博客程序对a标签做了处理,总之解决办法是使用span来代替a,这样就解决了这个问题。

      最后的代码在下面,这里提一下,按钮的样式是我从www.khanacademy.org照搬的

    View Code
      1 (function(global) {   2    3        if(global !== window) return;   4    5          6    7        function bind(context, name) {   8    9               return function() {  10   11                      return context[name].apply(context, arguments);  12   13               }  14   15        }  16   17         18   19        global.addEventListener('scroll', scrollHandler, false);  20   21         22   23        function scrollHandler() {  24   25               if(!scroll.isScrolling) {  26   27                      (scroll.getScrollY() > 0) ? scroll.show() : scroll.hide();  28   29               }  30   31        }  32   33         34   35        var scroll = {  36   37               _scrollY : 0,  38   39               isScrolling : false,  //is scrolling 40   41               imgBtn : null,  42   43               closeBtn : null,  44   45               create : function() {  46   47                      var div = global.document.createElement('div');  48   49                      var css = '#_scrollToTop{position:fixed;display:none;left:90%;top:80%;text-align:center;z-index:999999; 50px;height:50px;cursor:pointer;opacity:0.5;} #_scrollToTop:hover{opacity:1;} #_scrollToTop a{text-decoration:none;} #_scrollToTop span._arrow{background:none repeat scroll 0 0 #eee;border-style:solid; border-1px;border-color:#ccc #ccc #aaa; border-radius:5px;color:#333;font-size:36px;padding:5px 10px;} #_scrollToTop span._close {background:repeat scroll #548b02;position:absolute;top:-15px;right:-15px;border-radius:15px;border:1px solid #ccc;15px;height:15px;font-size:12px;text-align:center;visibility:hidden;}';  50   51                      GM_addStyle(css);  52   53                       54   55                      div.id = '_scrollToTop';  56   57                      div.title = 'Back To Top';  58   59                      div.innerHTML = '<span class="_close" title="hide this button">×</span><span class="_arrow">▲</span>';  60   61                      document.body.appendChild(div);  62   63                      div.addEventListener('click', bind(this, 'scrollToTop'),false);  64   65                      div.addEventListener('mouseover', bind(this, 'mouseOver'),false);  66   67                      div.addEventListener('mouseout', bind(this, 'mouseOut'),false);  68   69    70   71                      return this.imgBtn = div;         72   73               },  74   75               getImgBtn : function() {  76   77                      return this.imgBtn || this.create();  78   79               },  80   81               getCloseBtn : function() {  82   83                      return this.closeBtn || (this.closeBtn = this.getImgBtn().getElementsByTagName('span')[0]);  84   85               },  86   87               show : function() {  88   89                      this.getImgBtn().style.display = 'block';  90   91               },  92   93               hide : function() {  94   95                      this.getImgBtn().style.display = 'none';  96   97               },  98   99               mouseOver : function() { 100  101                      this.getCloseBtn().style.visibility = 'visible'; 102  103               }, 104  105               mouseOut : function() { 106  107                      this.getCloseBtn().style.visibility = 'hidden'; 108  109               }, 110  111               getScrollY : function() { 112  113                      //this piece of code is from John Resig's book 'Pro JavaScript Techniques'114  115                      var de = document.documentElement; 116  117                      return this._scrollY = (self.pageYOffset || 118  119                             ( de && de.scrollTop ) || 120  121                             document.body.scrollTop); 122  123               }, 124  125               scrollToTop : function(e) { 126  127                      if(e && e.target && e.target.getAttribute('class') === '_close') { 128  129                             //e.preventDefault();130  131                             this.hide(); 132  133                             global.removeEventListener('scroll', scrollHandler, false); 134  135                             return false; 136  137                      } else { 138  139                             if(!this.isScrolling) { 140  141                                    this.isScrolling = true; 142  143                             } 144  145                             this._scrollY -= 150; 146  147                             global.scrollTo(0, this._scrollY); 148  149                             if(this._scrollY > 0) { 150  151                                    setTimeout(bind(scroll, 'scrollToTop'), 20); 152  153                             } else { 154  155                                    this.isScrolling = false; 156  157                             } 158  159                      } 160  161               } 162  163        } 164  165 }(window.top))

     

      这里面多了一个隐藏按钮,点击X标志,可以让隐藏返回顶部这个功能,实际使用中发现,这个隐藏按钮的位置在许多网页都不相同,暂时没有去解决这个问题。

      我还对iframe做了一些检测,比如说我使用Gmail写邮件,或者用wordpress后台写博客,这个按钮总会出现在正文中,这是没有必要的,所以我判断只有当window和top值相等时,才让这个脚本继续运行,否则就返回。这样的结果就是在Gmail中按钮不会显示了:)

      另外,setTimeout的时间间隔为10毫秒,这个总感觉不是很对,因为对定时器还没有怎么仔细学习过,等把John Resig那本新书读完再说吧。

      最后附上这个脚本在UserScript网站的地址:http://userscripts.org/scripts/show/115493

  • 相关阅读:
    Css_加载样式
    Mvc4_@RenderBody()和@RenderSection()
    C#_观察者模式
    Mvc4_MvcPager 概述
    Mvc4_Area的应用
    Nginx 服务器性能参数设置
    Nginx变量的实现机制
    天下无雾
    Nginx Http框架的理解
    【转】websocket协议规范
  • 原文地址:https://www.cnblogs.com/youlechang123/p/2236811.html
Copyright © 2020-2023  润新知