小tip: 子元素scroll父元素容器不跟随滚动JS实现

这篇文章发布于 2015年12月18日,星期五,00:07,归类于 JS实例。 阅读 107012 次, 今日 6 次 47 条评论

 

一、开场暖身

网上常见蹲来蹲去的小段子,比方说:“李代沫蹲,李代沫蹲,李代沫蹲完黄海波蹲;黄海波蹲,黄海波蹲,黄海波蹲完宁财神蹲;宁财神蹲,宁财神蹲,宁财神蹲完张耀扬蹲;张耀扬蹲,张耀扬蹲,张耀扬蹲完郭美美蹲;郭美美蹲,郭美美蹲,郭美美蹲完……”。应该源自“萝卜蹲,萝卜蹲,萝卜蹲完苹果蹲……”。

在网页中,滚动条的滚动行为也是类似的调调,如果页面出现多个内嵌滚动条,则行为表现是:子元素滚,子元素滚,子元素滚完父元素滚;父元素滚,父元素滚,父元素滚完容器滚……

比方说下面:

在妹子脸上滚,先是妹子滚,妹子滚完主页面滚,对吧~

//zxx: 别问为什么不使用张含韵,因为张妹子照片是横的,滚动空间小,晓得伐~

这是浏览器的默认行为,如果我们遇到了一个需求:子元素滚,子元素滚完,就完了,父元素不需要滚了。那该如何实现呢?

在PC端,OK,本文介绍的方法,值适用于PC端,移动端,咳咳,我15年就没做过移动端项目,不好意思,手生,我也没去研究。

补充于翌日
移动端的处理,可以参见@hacke2的这篇文章:“在移动端上使用原生滑屏解决方案

二、阻止浏览器默认行为的特定套路

哈,本文标题有些拗口,实际上用一句话概括就是:如何阻止浏览器的默认滚动行为。

基本上,好像印象中就没有例外的,阻止浏览器的默认行为,就一条(假设事件对象参数是event):event.preventDefault().

这是标准规范使用方法。但是,对于老IE浏览器,event.returnValue = false. 如果你使用jQuery等框架,直接上面的event.preventDefault()就可以,库已经帮你搞定了兼容细节处理。

OK,回到本文。阻止默认滚动,也是类似,关键是找到准确的事件。

第一反应是scroll事件,不知道是不是我测试的方法不对,结果没鸟用;其实想想也可以理解,scroll事件要触发,尼玛必须已经滚动了哈~

后来,发现要从滚动事件的源头处理起来。在PC端,绝大多数滚动都是鼠标滚动触发的(上下快捷键也可以滚动页面,但一般人不知道),因此,我们可以从鼠标滚轮事件入手。

三、鼠标滚轮事件

JS基础知识的啦,mousewheel事件:

dom.onmousewheel = function() {
    // 嘿嘿嘿
};

IE, Chrome都认识,但是FireFox浏览器,要使用DOMMouseScroll, 具体知识呢我之前有写过文章分析过:“JS滚轮事件(mousewheel/DOMMouseScroll)了解”。现在回过头看看这篇文章,内容和点都挺好。但是,当时正好在学习模块化开发,以及JavaScript语言模式,所以,提供的代码,科科,不是拿来主义的调调,所以这篇文章没有火啊~

扯远了,总之呢,我们对鼠标滚动这个事件,进行event.preventDefault(),页面就像齿轮卡壳了一样,滚不动了!

四、原理爬上来

找到了关键钥匙,现在就要开门了。

子元素可以滚,父元素不能滚。

我们可以对子元素写上鼠标滚轮事件,对吧,的那个子元素滚动到边界的时候,我们立马插一刀event.preventDefault()。干掉整个页面的滚动,世界一下子安静了,时间好像突然静止了一般,好像很不错的样子哦!

于是,寡人我屁颠屁颠搞起代码(粗糙示意):

if (direction == 'up' && scrollTop == 0) { 
   event.preventDefault()
}

翻译下就是:哥哥我往上滚,当滚到头的时候,页面滚动歇菜。

Chrome一测试,喔噢,好棒,鼓掌! FireFox一测试,喔噢,好棒too,鼓掌again! IE一测试,喔噢,好…………尼玛,滚蛋了~ 滚动高度直接跳过了0,直接把父元素给滚了。

靠,什么鬼?不兼容,搞不定,怎么办?

五、临界手动翻滚

就是说,我们不要到0或者最大滚动高度时候,再去阻止默认滚动,我们要在到达边界的前一个滚动,就开始下手,手动滚动到边界,同时event.preventDefault()阻止鼠标滚动行为。于是,IE浏览器也棒棒哒了!

说实话,从开头到现在,中文啪啪啪敲了这么多,其实毛线用都没有,从度娘或谷哥过来的同学需要的不是什么神神叨叨的废话,需要的只是下面这段可以直接拿来主义的代码,好吧,拿去吧——子元素滚完就滚完的方法源代码:

$.fn.scrollUnique = function() {
    return $(this).each(function() {
        var eventType = 'mousewheel';
        // 火狐是DOMMouseScroll事件
        if (document.mozHidden !== undefined) {
            eventType = 'DOMMouseScroll';
        }
        $(this).on(eventType, function(event) {
            // 一些数据
            var scrollTop = this.scrollTop,
                scrollHeight = this.scrollHeight,
                height = this.clientHeight;

            var delta = (event.originalEvent.wheelDelta) ? event.originalEvent.wheelDelta : -(event.originalEvent.detail || 0);        

            if ((delta > 0 && scrollTop <= delta) || (delta < 0 && scrollHeight - height - scrollTop <= -1 * delta)) {
                // IE浏览器下滚动会跨越边界直接影响父级滚动,因此,临界时候手动边界滚动定位
                this.scrollTop = delta > 0? 0: scrollHeight;
                // 向上滚 || 向下滚
                event.preventDefault();
            }        
        });
    });	
};

没错,依赖jQuery的一个扩展方法,上面代码只要拷贝到你页面的JS中,然后,你希望哪个元素滚动到底,父级不滚动,直接:

$().scrollUnique();

就可以了,然后就可以打卡下班了。

对了,有个demo, 您可以狠狠地点击这里:里面元素滚动到底外部容器不滚动demo

如果您的显示器竖屏,或者宽度1920的,会发现右侧没有大滚动条,则,麻烦大家手动高度改小,拉拉窗口啊,或者打开控制台之类的。

//zxx: 你问我什么不加高页面造一个滚动条?唉,舍不得把底部的广告刻意藏在滚动条之外~

六、抛砖引玉

前文也提到,页面滚动条滚动的事件源很多,不仅仅是鼠标滚动,上下键,End键, Home键等都有滚动定位行为。因此,大家要想100%全方位封杀滚动行为,仅仅上面的鼠标滚动代码是不够的,但是,关键钥匙已经给大家了,大家可以依次,按照自己的项目需求进行进一步深入拓展。

不过,我个人觉得,上面mousewheel处理已经足够了,什么键盘触发滚动,让他自己去玩耍吧,还是别折腾了,吃力不讨好。

哟,写完了,抬头一看,一张截图都没有,这可不行,风水不能断,搞一张。

截图

恩,不错,真正的无可挑剔的「截」图。

完美

(本篇完)

分享到:


发表评论(目前47 条评论)

  1. lish说道:

    tips: 提醒一下大家,onwheel -> wheel

  2. vito说道:

    知道了一个新事件 …..

  3. 杨子聪说道:

    (event.originalEvent.wheelDelta) ? event.originalEvent.wheelDelta : -(event.originalEvent.detail || 0);

    这句代码目前在 Mac Chrome 是有问题的(windows 不知道),mousewheel 事件的 event 对象没有 originalEvent对 象,应该直接访问 event.wheelDelta。

    代码年代久远,已经跑不动了~

  4. 黑桃A说道:

    iframe有问题

  5. 嗯嗯说道:

    chrome中有bug,外层滚动时,内层未加top值,也随之滚动

  6. 大宝说道:

    原生的怎么写 大佬

  7. 依硕说道:

    大佬,牛逼~~~~

  8. 一条鱼说道:

    第一次看技术文觉得像在看段子
    哈哈哈
    开心地学到了知识
    谢谢博主!

  9. 大白菜说道:

    横向滚动咋办啊,还有mac上不用鼠标的情况咋整呢?

  10. 小白说道:

    方法有时候在火狐上不好使,是因为判断是否为火狐浏览器的方法有点问题,换其他方法判断火狐浏览器赋值type即可,比如 navigator.userAgent.indexOf(“Firefox”) > -1

    最后方法,亲测火狐,chrome,ie9可用
    $.fn.scrollUnique = function() {
    return $(this).each(function() {
    var eventType = ‘mousewheel’;
    // navigator.userAgent.indexOf(“Firefox”) > -1
    // document.mozHidden !== undefined
    if (navigator.userAgent.indexOf(“Firefox”) > -1) {
    eventType = ‘DOMMouseScroll’;
    }
    // console.log(eventType)
    $(this).on(eventType, function(event) {
    // 一些数据
    var scrollTop = this.scrollTop,
    scrollHeight = this.scrollHeight,
    height = this.clientHeight;

    var delta = (event.originalEvent.wheelDelta) ? event.originalEvent.wheelDelta : -(event.originalEvent.detail || 0);

    if ((delta > 0 && scrollTop <= delta) || (delta < 0 && scrollHeight – height – scrollTop 0? 0: scrollHeight;
    // 向上滚 || 向下滚
    // event.preventDefault();
    var e = window.event || event;
    if (e.stopPropagation){
    e.stopPropagation();
    }
    else {
    e.cancelBubble = true;
    }
    if (e.preventDefault){
    e.preventDefault();
    }
    else {
    e.returnValue = false;
    }
    }
    });
    });
    };

  11. ddd说道:

    demo在火狐不好使啊=。=

  12. cherry说道:

    event.preventDefault();
    该方法将通知 Web 浏览器不要执行与事件关联的默认动作。所以这不是很适合大部分页面设计,存在一定的bug,
    可以用下面代码替代
    var _e = window.event || e;
    if (e.stopPropagation){
    e.stopPropagation();
    }
    else {
    e.cancelBubble = true;
    }
    if (e.preventDefault){
    e.preventDefault();
    }
    else {
    e.returnValue = false;
    }

  13. chauncey说道:

    可以试试这个兼容360.
    (function($) {
    $.fn.scrollUnique = function() {
    return $(this).each(function() {
    var eventType = ‘mousewheel’;

    if(document.mozHidden !== undefined) {
    eventType = ‘DOMMouseScroll’;
    }
    $(this).on(eventType, function(e) {
    var e0 = e.originalEvent;
    var delta = e0.wheelDelta || -e0.detail;
    this.scrollTop += (delta < 0 ? 1 : -1)*120;
    e0.preventDefault();
    });
    });
    };
    })(jQuery);

    IF那个判断语句可以直接去掉.直接上 this.scrollTop += (delta < 0 ? 1 : -1)*120;亲测 360下快速滚动没毛病!

  14. Mr xie说道:

    我想问一下:如果这个设计到后台表格数据的时候。
    当前表格是iframe出现滚动条,当滚动的时候出发父级滚动条如何处理

  15. chen说道:

    直接在子元素scroll事件里阻止冒泡不可以吗?

  16. 刘朵朵说道:

    真的敲喜欢张真人的文章,看工具文还能这么一行一个笑点,真难为你了啊

  17. leekai说道:

    实测!在360浏览器, 版本8.1.1.240 内核版本 45.0.2454.101极速模式下
    上面的代码有问题, 当滚动鼠标滚轮速度比较慢时可能没啥大问题,但是当滚轮滚动的速度非常快的时候,就会发现页面还是会一起滚动,我在想是不是要做一个延迟处理!

  18. fiona说道:

    张师兄,看你的文章是一种享受~

  19. 会飞的胖子说道:

    知道原因了,wheelDelta的滚动距离是120,而图片高度又不超过,直达底部,就像没有缓冲一样直接到低了,如何解决继续看看-0-

  20. 会飞的胖子说道:

    前辈,发现在chrome里滚动不是很流畅,您当时使用的版本会吗?在moz的浏览器里相当流畅。
    目前是chrome56

  21. CandyQian说道:

    讲得很好,但是有些文章配图要有底线啊,上班时候看这个文章简直了

  22. BangKk说道:

    旭哥,还是那么幽默….

  23. 魔豆说道:

    多谢大神,学习啦

  24. 陈允东说道:

    学习了很多。。行文也很幽默,捐赠了

  25. 影子说道:

    貌似在移动端无效?

  26. 自命菜菜说道:

    大神的CSS文章有二百多篇了,有时候遇到问题回来看下,可是每次翻页找实在是太累了,可否整理个标题目录呢?

  27. test说道:

    新版的chrome已经不可用了。

  28. 路过说道:

    求兼容横向滚动

  29. skyy说道:

    张大哥,小弟不才,这今天在学javascipt,有个简单的问题请帮忙释疑解惑,不甚感谢。
    function getNextElement(node){
    if(node.nodeType == 1){
    return node
    }
    if(node.nextSibling){
    return getNextElement(node.nextSibling)
    }
    return null
    }
    这段函数中return getNextElement(node.nextSibling)这句怎么理解?函数里面包含函数自身的引用?

  30. GlacJAY说道:

    Firefox 桌面版貌似本身就已经是这样了?

  31. 前端小渣渣说道:

    才知道和大神来自一个小城镇,对大神更加钦佩了,学习ing,希望早日摆脱渣渣头衔

  32. Web前端之家说道:

    收藏学习了。哈哈

  33. deng说道:

    滚动定位的时候,你的代码是:this.scrollTop = delta > 0? 0: scrollHeight;

    但是,是否 this.scrollTop = delta > 0? 0: scrollHeight – scrollTop; 会更合理呢?谢谢!

  34. 哈哈说道:

    我有遇到这种疑惑过。左边是菜单(有滚动条),右边是套一个iframe,然后iframe滚动完,左边的菜单也跟着滚动。。。。不过我暂时还没找到办法解决,反正不是很重要,先做其他事情先了~~

    现在遇到个问题就是,想把整个网站整成只有一个css,但感觉还是挺有难度,页面太多,css也很多。。现在是慢慢弄一个system.css是通用的,比如弹窗、按钮、输入框、form表单横向布局、竖向布局、字体颜色等等~~然后有的页面感觉就是独立css布局的感觉,依赖于另外一个css文件,感觉是只能重写页面了。。(注:我是中途进这家公司的,刚好遇上系统重构,所以想把css这块也搞好。再注:我是看了你那篇2010写的那篇文章进来的)

  35. mantou说道:

    星哥 有一个问题。
    元素没有滚动条就不能滚动么?
    overflow hidden 了 怎么滚动元素,只能监听 wheel事件 改变top? 哦 还要加个内容框。但是这样好麻烦。
    有没有简单一点的方法?

  36. wind-stone说道:

    接上条,我看到“JS滚轮事件(mousewheel/DOMMouseScroll)了解”这篇文章了里有对event.delta相关的处理,wheelDelta除以了120,detail处理了3,最后event.delta为1是向上滚动,为-1是向下滚动。

  37. wind-stone说道:

    鼠标向下滚动时,wheelDelta为-120,而火狐detail为3;鼠标向上滚动时, wheelDelta为120,而火狐detail为-3。如果遇到非火狐的浏览器,请问这里的wheelDelta为什么不需要除以40?

    • demo说道:

      因为只需要判断是向上滚还是向下,所以只需要知道他们的正负就可以了,并不需要知道确切的值。

  38. dangdang说道:

    太棒啦,刚好在纠结这个问题,非常感谢~