原生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 对表格的行和列都能加亮显示
Dec 26 Javascript
JSChart轻量级图形报表工具(内置函数中文参考)
Oct 11 Javascript
用js来获取上传的文件名纯粹是为了美化而用
Oct 23 Javascript
一起学写js Calender日历控件
Apr 14 Javascript
详解VueJs异步动态加载块
Mar 09 Javascript
bootstrap table方法之expandRow-collapseRow展开或关闭当前行数据
Aug 09 Javascript
基于Require.js使用方法(总结)
Oct 26 Javascript
vue-cli3配置与跨域处理方法
Aug 17 Javascript
vuex vue简单使用知识点总结
Aug 29 Javascript
微信小程序进入广告实现代码实例
Sep 19 Javascript
vue实现tab栏点击高亮效果
Aug 19 Javascript
js前端对于大量数据的展示方式及处理方法
Dec 02 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 XML数据解析代码
2010/05/26 PHP
php调用MySQL存储过程的方法集合(推荐)
2013/07/03 PHP
destoon首页调用求购供应信息的地区名称的方法
2014/08/21 PHP
php字符串比较函数用法小结(strcmp,strcasecmp,strnatcmp及strnatcasecmp)
2016/07/18 PHP
php递归函数怎么用才有效
2018/02/24 PHP
超级退弹代码
2008/07/07 Javascript
响应鼠标变换表格背景或者颜色的代码
2009/03/30 Javascript
JQuery this 和 $(this) 的区别
2009/08/23 Javascript
如何解决手机浏览器页面点击不跳转浏览器双击放大网页
2016/07/01 Javascript
浅谈AngularJs指令之scope属性详解
2016/10/24 Javascript
jquery实现点击页面回到顶部
2016/11/23 Javascript
Bootstrap实现的经典栅格布局效果实例【附demo源码】
2017/03/30 Javascript
在vue中通过axios异步使用echarts的方法
2018/01/13 Javascript
从Vuex中取出数组赋值给新的数组,新数组push时报错的解决方法
2018/09/18 Javascript
JS中准确判断变量类型的方法
2020/06/01 Javascript
vue中解决拖拽改变存在iframe的div大小时卡顿问题
2020/07/22 Javascript
[38:23]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS LGD第一场
2014/05/24 DOTA
[40:19]完美世界DOTA2联赛PWL S3 Rebirth vs CPG 第二场 12.18
2020/12/19 DOTA
python del()函数用法
2013/03/24 Python
python爬虫框架scrapy实战之爬取京东商城进阶篇
2017/04/24 Python
Python常用爬虫代码总结方便查询
2019/02/25 Python
python matplotlib如何给图中的点加标签
2019/11/14 Python
amazeui时间组件的实现示例
2020/08/18 HTML / CSS
ZWILLING双立人法国网上商店:德国刀具锅具厨具品牌
2019/08/28 全球购物
包装类的功能、种类、常用方法
2012/01/27 面试题
经典优秀毕业生求职信范文分享
2013/12/18 职场文书
经典导游欢迎词大全
2014/01/16 职场文书
2014年五四青年节活动方案
2014/03/29 职场文书
宿舍标语大全
2014/06/19 职场文书
买卖合同协议书范本
2014/10/18 职场文书
离职报告范文
2014/11/04 职场文书
2014年综治维稳工作总结
2014/11/17 职场文书
优秀班主任材料
2014/12/16 职场文书
高考学习决心书
2015/02/04 职场文书
2015年财政局工作总结
2015/05/21 职场文书
LeetCode189轮转数组python示例
2022/08/05 Python