原生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 相关文章推荐
轻轻松松学习JavaScript
Feb 25 Javascript
javascript 新浪背投广告实现代码
Jul 07 Javascript
js工具方法弹出蒙版
May 08 Javascript
jquery next nextAll nextUntil siblings的区别介绍
Oct 05 Javascript
jQuery取得设置清空select选择的文本与值
Jul 08 Javascript
jQuery层级选择器用法分析
Feb 10 Javascript
javascript瀑布流式图片懒加载实例解析与优化
Feb 23 Javascript
javascript 中的try catch应用总结
Apr 01 Javascript
Angular2自定义分页组件
Apr 19 Javascript
jquery 通过ajax请求获取后台数据显示在表格上的方法
Aug 08 jQuery
vue 弹窗时 监听手机返回键关闭弹窗功能(页面不跳转)
May 10 Javascript
Layui 动态禁止select下拉的例子
Sep 03 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
用 Composer构建自己的 PHP 框架之使用 ORM
2014/10/30 PHP
php操作xml入门之xml标签的属性分析
2015/01/23 PHP
PHP的Yii框架中使用数据库的配置和SQL操作实例教程
2016/03/17 PHP
PHP实现简单实用的分页类代码
2016/04/08 PHP
实例分析PHP将字符串转换成数字的方法
2019/01/27 PHP
PHP正则之正向预查与反向预查讲解与实例
2020/04/06 PHP
JavaScript 指导方针
2007/04/05 Javascript
几个有趣的Javascript Hack
2010/07/24 Javascript
jquery实现checkbox全选全不选的简单实例
2013/12/31 Javascript
利用jQuery插件imgAreaSelect实现图片上传裁剪(放大缩小)
2016/12/02 Javascript
原生JS和jQuery操作DOM对比总结
2017/01/19 Javascript
bootstrap table实现双击可编辑、添加、删除行功能
2017/09/27 Javascript
vue.js实现的绑定class操作示例
2018/07/06 Javascript
微信小程序中使用wxss加载图片并实现动画效果
2018/08/13 Javascript
jQuery实现的点击图片居中放大缩小功能示例
2019/01/16 jQuery
JS常用排序方法实例代码解析
2020/03/03 Javascript
vue将data恢复到初始状态 &amp;&amp; 重新渲染组件实例
2020/09/04 Javascript
JavaScript 防盗链的原理以及破解方法
2020/12/29 Javascript
js实现类选择器和name属性选择器的示例步骤
2021/02/07 Javascript
[56:00]2018DOTA2亚洲邀请赛 4.6 淘汰赛 VP vs TNC 第二场
2018/04/10 DOTA
Python中的异常处理try/except/finally/raise用法分析
2019/02/28 Python
Python datetime和unix时间戳之间相互转换的讲解
2019/04/01 Python
python获取当前文件路径以及父文件路径的方法
2019/07/10 Python
Python定时任务随机时间执行的实现方法
2019/08/14 Python
Django 导出项目依赖库到 requirements.txt过程解析
2019/08/23 Python
python文件排序的方法总结
2020/09/13 Python
用HTML5实现网站在windows8中贴靠的方法
2013/04/21 HTML / CSS
加拿大折扣、优惠券和交易网站:WagJag
2018/02/07 全球购物
俄罗斯购买自行车网站:Vamvelosiped
2021/01/29 全球购物
进修护士自我鉴定
2013/10/14 职场文书
部队万能检讨书
2014/02/20 职场文书
出纳工作检讨书范文
2014/12/27 职场文书
第一书记观后感
2015/06/08 职场文书
党员电教片《信仰》心得体会
2016/01/15 职场文书
创业计划书之宠物店
2019/09/19 职场文书
JS轻量级函数式编程实现XDM二
2022/06/16 Javascript