原生js实现模拟滚动条


Posted in Javascript onJune 15, 2015

当页面中有很多滚动条,它们相互嵌套,很不好看,这时就会模拟滚动条,并给这个滚动条好看的样式,使得页面美观。

模拟滚动条很多时候是去用jquery插件,然后写几行代码就搞定了。不过随着mvvm的快速发展,很多时候都懒得用jquery了,这就是本文的动机,本?帕η笥眉虻サ牟灰览?query只依赖mvvm(avalon) api的代码,完成一个简易的滚动条。

要求:

1.鼠标滚轮可以让滚动条工作,界面滚动

2.鼠标可以拖动滚动条并让界面滚动

3.页面resize时,滚动条根据页面尺寸变化,仍然可以工作

效果:

原生js实现模拟滚动条

很显然,这个组件是基于拖动drag的,本?庞植幌胫匦滦矗?椭挥懈南?i框架的drag了,这里改的是easy js ui的drag组件。用easy js是因为注释比较多,代码简洁。

本?虐?asy js ui的drag组件里的相应方法换成avalon api里的方法,删掉prototype里的方法及冗余代码

define('drag',['avalon-min'],function(avalon){
  function getBoundary(container, target) {
    var borderTopWidth = 0, borderRightWidth = 0, borderBottomWidth = 0, borderLeftWidth = 0, cOffset = avalon(container)
    .offset(), cOffsetTop = cOffset.top, cOffsetLeft = cOffset.left, tOffset = avalon(target)
    .offset();
    borderTopWidth = parseFloat(avalon.css(container,'borderTopWidth'));
    borderRightWidth = parseFloat(avalon.css(container,'borderRightWidth'));
    borderBottomWidth = parseFloat(avalon.css(container,'borderBottomWidth'));
    borderLeftWidth = parseFloat(avalon.css(container,'borderLeftWidth'));
    cOffsetTop = cOffsetTop - tOffset.top + parseFloat(avalon(target).css('top'));
    cOffsetLeft = cOffsetLeft - tOffset.left + parseFloat(avalon(target).css('left'));
    return {
      top : cOffsetTop + borderTopWidth,
      right : cOffsetLeft + avalon(container).outerWidth() - avalon(target).outerWidth()
      - borderRightWidth,
      left : cOffsetLeft + borderLeftWidth,
      bottom : cOffsetTop + avalon(container).outerHeight() - avalon(target).outerHeight()
      - borderBottomWidth
    };
  }
  var drag = function(target, options) {
    var defaults = {
      axis:null,
      container:null,
      handle:null,
      ondragmove:null
    };
    var o =avalon.mix(defaults,options),
    doc = target.ownerDocument,
    win = doc.defaultView || doc.parentWindow,
    originHandle=target,
    isIE =!-[1,],
    handle = isIE ? target :doc,
    container = o.container ?o.container: null, 
    count = 0,
    drag = this,    
    axis = o.axis,    
    isMove = false, 
    boundary, zIndex, originalX, originalY,
    clearSelect = 'getSelection' in win ? function(){
      win.getSelection().removeAllRanges();
    } : function(){
      try{
        doc.selection.empty();
      }
      catch( e ){};
    },
    down = function( e ){
      o.isDown = true;        
      var newTarget = target,
      left, top, offset;
      o.width = avalon(target).outerWidth();
      o.height = avalon(target).outerHeight();
      o.handle = handle;
      left = avalon(newTarget).css( 'left' );
      top = avalon(newTarget).css( 'top' );     
      offset = avalon(newTarget).offset();
      drag.left = left = parseInt( left );
      drag.top = top = parseInt( top );
      drag.offsetLeft = offset.left;
      drag.offsetTop = offset.top;
      originalX = e.pageX - left;
      originalY = e.pageY - top; 
      if( (!boundary && container)){
        boundary = getBoundary(container, newTarget ); 
      } 
      if( axis ){
        if( axis === 'x' ){
          originalY = false;
        }
        else if( axis === 'y' ){
          originalX = false;
        }
      }
      if( isIE ){
        handle.setCapture();
      }
      avalon.bind(handle,'mousemove',move);
      avalon.bind(handle,'mouseup',up);
      if( isIE ){
        avalon.bind(handle,'losecapture',up);
      }
      e.stopPropagation();
      e.preventDefault();  
    },
    move = function( e ){
      if( !o.isDown ){
        return;
      }      
      count++;
      if( count % 2 === 0 ){
        return;
      }
      var currentX = e.pageX,
      currentY = e.pageY,
      style = target.style,
      x, y, left, right, top, bottom;
      clearSelect();
      isMove = true;
      if( originalX ){
        x = currentX - originalX;
        if( boundary ){
          left = boundary.left;
          right = boundary.right;
          x = x < left ? left : 
          x > right ? right :
          x;
        }  
        drag.left = x;
        drag.offsetLeft = currentX - e.offsetX;
        style.left = x + 'px';
      }
      if( originalY ){
        y = currentY - originalY;
        if( boundary ){
          top = boundary.top;
          bottom = boundary.bottom;
          y = y < top ? top : 
          y > bottom ? bottom :
          y;
        }  
        drag.top = y;
        drag.offsetTop = currentY - e.offsetY;
        style.top = y + 'px';
      }
      o.ondragmove.call(this,drag);
      e.stopPropagation();  
    },
    up = function( e ){
      o.isDown = false;
      if( isIE ){
        avalon.unbind(handle,'losecapture' );
      }
      avalon.unbind( handle,'mousemove');
      avalon.unbind( handle,'mouseup');
      if( isIE ){
        handle.releaseCapture();
      }
      e.stopPropagation();        
    }; 
    avalon(originHandle).css( 'cursor', 'pointer' );
    avalon.bind( originHandle,'mousedown', down );
    drag.refresh=function(){
      boundary=getBoundary(container,target);
    };  
  };
  return drag;
});

另外在最后暴露的drag上加了一个refresh()方法,作用是在resize时,需要更新滚动条可以拖动的范围。这个方法在scrollbar的更新视图中会用到。

drag.refresh=function(){
      boundary=getBoundary(container,target);
    };

还有在滚动条拖动过程move中,添加一个钩子,允许从外面添加一个监听函数,拖动时会触发监听函数,并传入drag参数。

o.ondragmove.call(this,drag);

然后是scrollbar.js

define('scrollbar',['avalon-min','drag'],function(avalon,drag){
  function scrollbar(wrap,scrollbar,height_per_scroll){//容器,滚动条,每次滚轮移动的距离
    this.scroll_height=0;//滚动条高度
    this.dragger=null;//drag组件实例
    wrap.scrollTop=0;
    //容器的位置要减去浏览器最外面的默认滚动条垂直方向位置
    var self=this,wrap_top=avalon(wrap).offset().top-avalon(document).scrollTop();
    function ondragmove(drag){//drag组件拖动时的监听函数,更新容器视图
      wrap.scrollTop=(parseFloat(scrollbar.style.top)-wrap_top)*
      (wrap.scrollHeight -wrap.clientHeight)/(wrap.clientHeight-self.scroll_height);
    };
    function setScrollPosition(o) {//更新滚动条位置
      scrollbar.style.top =o.scrollTop*wrap.clientHeight/wrap.scrollHeight+wrap_top+ 'px';
    }
    function inti_events(){
      avalon.bind(wrap,'mousewheel',function(e){
        if(e.wheelDelta < 0)
          wrap.scrollTop+=height_per_scroll;
        else
          wrap.scrollTop-=height_per_scroll;
        setScrollPosition(wrap);
        e.preventDefault(); 
      });
      self.dragger=new drag(scrollbar,{container:wrap,axis:'y',ondragmove:ondragmove});
      window.onresize=function(){
        self.refresh_views();
        self.dragger.refresh();
      };
    }
    this.refresh_views=function(){//更新组件所有部分视图,并暴露供外部调用
      //容器高度这里设置成浏览器可视部分-容器垂直方向位置,没有考虑容器有border,padding,margin.可根据相应场景修改
      wrap.style.height=document.documentElement.clientHeight-wrap_top+'px';
      self.scroll_height=wrap.clientHeight*wrap.clientHeight/wrap.scrollHeight;
      //容器高度等于滚动条高度,隐藏滚动条
      if(self.scroll_height==wrap.clientHeight)
        scrollbar.style.display='none';
      else
        scrollbar.style.display='block';
      scrollbar.style.height=self.scroll_height+'px';
      setScrollPosition(wrap);
    }
    function init(){
      self.refresh_views();
      inti_events();
    }
    init();
  }
  return scrollbar;
});

可以看到,在resize时,调用了drag组件的refresh方法,更新滚动条可以拖动的范围。这里暴露了refresh_views()方法,以应对外部需要手动更新视图的情况。比如,聊天分组的折叠和展开。

原生js实现模拟滚动条

这样就完成了简易滚动条。代码很简单,如果出问题需要fix bug或定制的话,也很容易。

以上所述上就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
Discuz! 6.1_jQuery兼容问题
Sep 23 Javascript
Jquery Ajax学习实例7 Ajax所有过程事件分析示例
Mar 23 Javascript
js的延迟执行问题分析
Jun 23 Javascript
更快的异步执行(setTimeout多浏览器)
Aug 12 Javascript
JavaScript闭包详解
Feb 02 Javascript
jquery验证邮箱格式是否正确实例讲解
Nov 16 Javascript
实例讲解使用原生JavaScript处理AJAX请求的方法
May 10 Javascript
jQuery实现可展开折叠的导航效果示例
Sep 12 Javascript
微信小程序 es6-promise.js封装请求与处理异步进程
Jun 12 Javascript
简单理解Vue中的nextTick方法
Jan 30 Javascript
Bootstrap实现模态框效果
Sep 30 Javascript
js实现九宫格布局效果
May 28 Javascript
jquery插件unobtrusive实现片段式加载
Jun 15 #Javascript
js预加载图片方法汇总
Jun 15 #Javascript
jquery实现兼容IE8的异步上传文件
Jun 15 #Javascript
JavaScript中使用Math.floor()方法对数字取整
Jun 15 #Javascript
jQuery实现自动调整字体大小的方法
Jun 15 #Javascript
JavaScript中使用指数方法Math.exp()的简介
Jun 15 #Javascript
Jquery简单实现GridView行高亮的方法
Jun 15 #Javascript
You might like
Mysql的常用命令
2006/10/09 PHP
网站加速 PHP 缓冲的免费实现方法
2006/10/09 PHP
PHP XML备份Mysql数据库
2009/05/27 PHP
iis下php mail函数的sendmail配置方法(官方推荐)
2012/04/25 PHP
PHP微信开发之查询城市天气
2016/06/23 PHP
Laravel框架集成UEditor编辑器的方法图文与实例详解
2019/04/17 PHP
Flash+XML滚动新闻代码 无图片 附源码下载
2007/11/22 Javascript
ExtJS 2.2.1的grid控件在ie6中的显示问题
2009/05/04 Javascript
javascript针对DOM的应用分析(三)
2012/04/15 Javascript
js进行表单验证实例分析
2015/02/10 Javascript
js生成验证码并直接在前端判断
2015/05/15 Javascript
基于Jquery和CSS3制作数字时钟附源码下载(CSS3篇)
2015/11/24 Javascript
浅谈Vue数据响应思路之数组
2018/11/06 Javascript
微信小程序实现工作时间段选择
2019/02/15 Javascript
24行JavaScript代码实现Redux的方法实例
2019/11/17 Javascript
在vue中使用Base64转码的案例
2020/08/07 Javascript
python实现zencart产品数据导入到magento(python导入数据)
2014/04/03 Python
Python itertools模块详解
2015/05/09 Python
Python实现的序列化和反序列化二叉树算法示例
2019/03/02 Python
python中如何使用分步式进程计算详解
2019/03/22 Python
Dlib+OpenCV深度学习人脸识别的方法示例
2019/05/14 Python
django mysql数据库及图片上传接口详解
2019/07/18 Python
Python英文文章词频统计(14份剑桥真题词频统计)
2019/10/13 Python
将python文件打包exe独立运行程序方法详解
2020/02/12 Python
Python 字符串池化的前提
2020/07/03 Python
酒店管理毕业生自荐信
2013/10/24 职场文书
读书心得体会
2013/12/28 职场文书
环保建议书
2014/03/12 职场文书
关爱女孩行动实施方案
2014/03/13 职场文书
师德建设实施方案
2014/03/21 职场文书
施工安全责任书
2014/04/14 职场文书
职工擅自离岗检讨书
2014/09/23 职场文书
小学秋季运动会报道稿
2014/09/30 职场文书
个人对照检查剖析材料
2014/10/13 职场文书
幼儿园校车安全责任书
2015/05/08 职场文书
2015年音乐教学工作总结
2015/07/22 职场文书