原生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 相关文章推荐
js验证表单第二部分
Nov 25 Javascript
jquery中eq和get的区别与使用方法
Apr 14 Javascript
TimergliderJS 一个基于jQuery的时间轴插件
Dec 07 Javascript
javascript面向对象特性代码实例
Jun 12 Javascript
jquery事件preventDefault()方法用法实例
Jan 16 Javascript
JS面向对象(3)之Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法
Feb 25 Javascript
javascript的replace方法结合正则使用实例总结
Jun 16 Javascript
react配合antd组件实现的管理系统示例代码
Apr 24 Javascript
详解vue+webpack+express中间件接口使用
Jul 17 Javascript
基于Angularjs-router动态改变Title值的问题
Aug 30 Javascript
理顺8个版本vue的区别(小结)
Sep 17 Javascript
vue render函数动态加载img的src路径操作
Oct 26 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
php环境配置之CGI、FastCGI、PHP-CGI、PHP-FPM、Spawn-FCGI比较?
2011/10/17 PHP
解析php mysql 事务处理回滚操作(附实例)
2013/08/05 PHP
php多次include后导致全局变量global失效的解决方法
2015/02/28 PHP
PHP流Streams、包装器wrapper概念与用法实例详解
2017/11/17 PHP
页面加载完后自动执行一个方法的js代码
2014/09/06 Javascript
jQuery实现跨域
2015/02/03 Javascript
javascript实现全局匹配并替换的方法
2015/04/27 Javascript
JavaScript实现Iterator模式实例分析
2015/06/09 Javascript
Node.js的项目构建工具Grunt的安装与配置教程
2016/05/12 Javascript
移动端基础事件总结与应用
2017/01/12 Javascript
JavaScript中 DOM操作方法小结
2017/04/25 Javascript
细说webpack源码之compile流程-rules参数处理技巧(1)
2017/12/26 Javascript
通过webpack引入第三方库的方法
2018/07/20 Javascript
vue-cli的工程模板与构建工具详解
2018/09/27 Javascript
微信小程序实现张图片合成为一张并下载
2019/07/16 Javascript
layui点击数据表格添加或删除一行的例子
2019/09/12 Javascript
Vue使用富文本编辑器Vue-Quill-Editor(含图片自定义上传服务、清除复制粘贴样式等)
2020/05/15 Javascript
python抓取网页时字符集转换问题处理方案分享
2014/06/19 Python
Python递归遍历列表及输出的实现方法
2015/05/19 Python
Python程序中用csv模块来操作csv文件的基本使用教程
2016/03/03 Python
详解python使用Nginx和uWSGI来运行Python应用
2018/01/09 Python
在pycharm中使用git版本管理以及同步github的方法
2019/01/16 Python
详解Python3中ceil()函数用法
2019/02/19 Python
Python实现使用request模块下载图片demo示例
2019/05/24 Python
python将字母转化为数字实例方法
2019/10/04 Python
Python算法中的时间复杂度问题
2019/11/19 Python
Pytorch转tflite方式
2020/05/25 Python
迪卡侬英国官网:Decathlon英国
2017/04/08 全球购物
澳大利亚顶级美发和美容贸易超市:glamaCo
2020/01/19 全球购物
幼儿园家长评语
2014/02/10 职场文书
2016入党培训心得体会范文
2016/01/08 职场文书
2016中秋节广告语
2016/01/28 职场文书
机关单位2016年法制宣传日活动总结
2016/04/01 职场文书
导游词之秦皇岛燕塞湖
2020/01/03 职场文书
MySQL索引是啥?不懂就问
2021/07/21 MySQL
Python按顺序遍历并读取文件夹中文件
2022/04/29 Python