小tip:CSS vw让overflow:auto页面滚动条出现时不跳动

这篇文章发布于 2015年01月25日,星期日,23:08,归类于 CSS相关。 阅读 198403 次, 今日 27 次 71 条评论

 

补充于2022-01-12

Chrome 浏览器支持了一个新的 CSS 属性 scrollbar-gutter 专门解决此问题,详见这篇文章

一、水平居中布局与滚动条跳动的千年难题

当前web届,绝大多数的页面间布局都是水平居中布局,主体定个宽度,然后margin: 0 auto的节奏~

例如,妇女之友大淘宝的首页:
淘宝首页的居中布局

然而,这种布局有一个存在一个影响用户体验的隐患。应该都知道,现代浏览器滚动条默认是overflow:auto类型的,也就是如果尺寸不足一屏,没有滚动条;超出,出现滚动条。于是,问题来了:

  1. 信息流页面,如新浪微博,是从上往下push渲染的。开始只有头部一些信息加载,此时页面高度有限,没有滚动条;然后,更多内容显示,滚动条出现,占据可用宽度,margin: 0 auto主体元素自然会做偏移——跳动产生。
  2. JS交互,本来默认页面高度不足一屏,结果点击了个“加载更多”,内容超过一屏,滚动条出现,页面主体就会左侧跳动。
  3. 结构类似几个页面通过头部的水平导航刷新切换,结果有的页面有滚动条,有的没有。造成的结果就是,导航尼玛怎么跳来跳去!

当前优化这种体验问题,一般有两种解决方法:

  1. 高度尺寸不确定的,例如,新浪微博,使用:
    body { overflow-y: scroll; }

    新浪微博处理滚动跳动问题方法

  2. 高度确定的,例如淘宝网首页。使用CSS把页面尺寸布局骨架搭好,再在里面吐数据。于是,要么没有滚动条,要么滚动条直接出现。不会出现跳动。
    淘宝网首页骨架示意

然而,然而,后面的策略只适合一些特殊的定制性很强的页面。你说像知乎这样子,高度随内容而定的页面,显然就无法驾驭;而第1种方法overflow-y: scroll,在页面高度较小的时候,依然会保留一个丑陋的灰色的滚动栏,这其实又回到了IE当道的旧社会时代。现代浏览器做的那些默认视觉优化岂不是白费了,想想就好痛心。

痛心

大师,难道就没有一了百了、两全其美、三生有幸的方法了吗?

阿弥陀佛,骚年,请看我手中的这盏灯……

二、CSS3计算calc和vw单位巧妙实现滚动条出现页面不跳动

很简单,只要一行代码就搞定了:

.wrap-outer {
    margin-left: calc(100vw - 100%);
}

或者:

.wrap-outer {
    padding-left: calc(100vw - 100%);
}

然后就可以庆祝放鞭炮啦!!

首先.wrap-outer指的是居中定宽主体的父级,如果没有,创建一个(使用主体也是可以实现类似效果,不过本着宽度分离原则,不推荐);
然后calc是CSS3中的计算,IE10+浏览器支持,IE9浏览器基本支持(不能用在background-position上);
最后100vw相对于浏览器的window.innerWidth,是浏览器的内部宽度,注意,滚动条宽度也计算在内!而100%是可用宽度,是不含滚动条的宽度。
于是calc(100vw - 100%)就是浏览器滚动条的宽度大小(如果有,如果没有滚动条则是0)!左右都有一个滚动条宽度(或都是0)被占用,主体内容就可以永远居中浏览器啦,从而没有任何跳动!

您可以狠狠地点击这里(IE10+):页面出现滚动条的时候没有跳动demo

demo页面中,标题和下面的妹子都是居中效果。其中,妹子做了本文所述的“滚动无跳动”处理,而标题没有,结果,你会发现,滚动条出现与否会让标题文字跳动,但是,妹子却女神般岿然不动:
页面滚动条出现不会跳动截图

兼容性
支持:IE9+以及其他现代浏览器。

窄屏幕宽度下的处理
上面CSS还是有一点瑕疵的,浏览器宽度比较小的时候,左侧留的白明显与右边多,说不定会显得有点傻。此时,可能需要做点响应式处理会更好一点:

@media screen and (min-width: 1150px) {
   .wrap-outer {
       margin-left: calc(100vw - 100%);
   }
}

更新于2016年9月28日
经过一些列项目实践,关于浏览器出现滚动条和消失页面不滚动有了更加终极的解决方案,经过大型项目实践已经验证相当具有可行性,这里特意分享下:

html {
  overflow-y: scroll;
}

:root {
  overflow-y: auto;
  overflow-x: hidden;
}

:root body {
  position: absolute;
}

body {
  width: 100vw;
  overflow: hidden;
}

大家随意取走,不用谢!

三、结束语

本文参考自:Fix ‘jumping scrollbar’ issue using only CSS

说点八卦吧,本文原作者名叫Ayke van Laëthem, 才开始写博客,就是因为写了这篇文章,搞掉了他1G的带宽流量,文章至少访问了15,000次,着实被吓着了,还是蛮搞的!哈哈!

如果你也有精彩创意且使用的前端tips, 不妨也拿出来共享下,会有很多意外的收获与成长的。

(本篇完)

分享到:


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

  1. yanxugong说道:

    overflow: hidden; 会导致 position: sticky; 失效。

  2. 吃个大西瓜说道:

    overflow: hidden;

    会导致部分元素,比如下拉列表不显示了

  3. 来了说道:

    怎么让页面内一个普通div的滚动条,不抖动,一个普通div用不了100vw

  4. 梦懵锰说道:

    前辈,MDN上说,:root表示html元素,除了更高级别之外,与html选择器相同。那对html和root伪类分别设置了overflow-y,有什么意义呢?我不是很理解,希望能得到讲解。

  5. 光影说道:

    只考虑 chrome / safari / opera,不考虑 firefox 的话 可以用overflow: overlay 这个实验属性 解一解燃眉之急

  6. Fong说道:

    macOS 上开发时发现,系统偏好设置将影响滚动条是否占据实际水平空间,通用 > 显示滚动条如果不是为始终,出现滚动条将不占据空间,bodyWidth : 100% = 100vw,不会出现跳动的情况。

  7. 太想飞说道:

    旭哥,能否讲一下终极方案的原理

    • 光影说道:

      我猜想 :root body 所做的处理就是让body新建一个层,这个层的内容和 滚动条的内容不会相互影响,此时给:root加上滚动样式,可以完成效果

    • 说道:

      其实就是和别的方案一样先把滚动条的位置留出来(对HTML设置scroll)。因为position的缘故,在下面对body设置width: 100vw;的时候一方面这个100就不是相对整个视窗了;另一方面这时候body已经脱离文档流在一个单独的渲染层中了。如果还不理解可以参考张大之前关于HTML、body关系的文章

  8. anghunk说道:

    css确实博大精深,能够解决很多浏览器体验不好的问题,但是重视的人不多

  9. Arlo Liu说道:

    2016年9月的大神分享的,今天2020年10月依然管用 我是wordpress

  10. bella说道:

    真·优质文章
    困扰了很久的滚动条抖动问题终于解决了,用的是最新更新的方案。虽然解决了,但原理不是很懂,前端之路对小白来说真是道阻且长。。

    • 淡然说道:

      所以我才说,CSS真的是门玄学。JS好歹有问题就是有问题,没问题就是没问题。该怎样怎样。CSS真的是又爱又恨,估计也只有张大神才能把它玩弄于鼓掌中。

  11. joinmouse说道:

    完美解决我的问题哦,谢谢

  12. 说道:

    这样写的话,弹框下的页面页可以滚动啦~

  13. 谢大帅哥说道:

    用了一个比较贱贱的方法来解决了当网站缩小时,横向滚动条没有显示

    @media screen and (min-width: 1040px) {

    :root {
    overflow-x: hidden;
    }
    body {
    overflow: hidden;
    }
    }

    html {
    overflow-y: scroll;
    }

    :root {
    overflow-y: auto;
    }

    :root body {
    position: absolute;
    }

    body {
    width: 100vw;
    }

  14. 中国梦说道:

    .wrap-outer {
    padding-left: calc(100vw – 100%);
    }
    这个方案有什么缺陷吗

  15. paranoidyang说道:

    有个bug,当网站缩小时,横向滚动条没有显示,请问一下怎么解决???

  16. 说道:

    终极方案会使横向滚动条失效,当窗体缩小的时候,如果中间容器有最小宽度的话,无法左右滚动看全所有内容

  17. 张老师?这是啥?说道:

    查了下:root是选择器指html,那么
    html {
    overflow-y: scroll;
    }
    不是会被
    :root {
    overflow-y: auto; //被这句覆盖?为什么要一起写呢?
    overflow-x: hidden;
    }
    后面倒感觉理解一点,body设置width:100vw是为了让body撑满整个页面包括滚动条宽度,这样出现滚动条也不会让页面内容向左跳动,而:root body定位本来不能理解后来试了一下是为了解决当body设置width:100vw造成页面内容足够宽而被滚动条遮挡的问题。
    看到下面评论360不支持的,碰到这种好多人在用的个别浏览器我们该怎么办呢?

    • 光影说道:

      :root 和 html 的支持版本不一样,概念其实也有区别,这里应该就是考虑到兼容性问题

  18. Roam说道:

    在 Bootstrap3 中,弹出模态框 Modal,手动拉伸浏览器高度,滚动条出现不抖动。
    但是当模态框里面的内容改变引起的高度变化,则会抖动。

    Bootstrap 实现上是计算滚动条宽度,然后给 body 设置 overflow:hidden; padding-right: 17px;(滚动条宽度),以此保证 body 内容不滚动。
    但是弹出层是 fixed 全屏覆盖的,里面是滚动 auto

    因为 body 上设置了 overflow:hidden,导致计算为 0;

    抖的好蛋疼..
    有兴趣的看看
    http://v3.bootcss.com/javascript/#动态实例

    • Roam说道:

      我想到一个新方法,既然中间部分是左右居中的,为什么不使用:
      margin-left: 50vw;
      transform: translateX(-50%);
      这样可以不需要关心父级的滚动条

  19. :root说道:

    :root 是什么呢

  20. 前端入门说道:

    如果主体部分设置了最小宽度比如:1200px,终极解决方案不会出现水平滚动条,而vw方案,在1250-1150的范围内还是会发生跳动,求大神解疑

  21. fixed定位无作用说道:

    当position:fixed的时候,终极代码就不起作用了。求解

  22. 吃水不忘挖井人说道:

    程序猿之宝,张大婶

  23. 四海说道:

    最终解决方案里为何要加body绝对定位呢?
    :root body {
    position: absolute;
    }

  24. 哈哈说道:

    太感动了,,找了那么久终于找到了完美解决方案,,感动哭,,从此以后你是我偶像~~~~

  25. welcan说道:

    厉害啊,大赞一个!

  26. future说道:

    终极解决方案可用,已收藏

  27. 酱油猫说道:

    更新于2016年9月28日 可用

  28. 小金说道:

    亲,表格出现滚动条的时候,不影响表格内容的宽度应该怎么写呢??

  29. 刘继芬说道:

    还有另外一种解决思路,http://blog.w3cedu.net/chenhao/blog/2016-11-30/51.html

  30. 小黄说道:

    根据2016年9月28日更新,我写了一个demo,从demo得知,页面在没有滚动条时,工作很好,当页面出现滚动条时,靠右对齐的内容会被滚动条挡住,求教博主这样的缺陷你们是如何修复的呢?
    或者是我的例子有问题?

    相关demo可点击查看:https://jsfiddle.net/c32wswzL/
    在开发者工具中尝试收缩页面高度,当 result 部分出现滚动条会遮挡靠右对齐的文字内容。

    期待您的回复。

    • 张 鑫旭说道:

      一般网页都会在左右留有一点的间隙的。

      • 小黄说道:

        后面想明白了,感谢您的回复!
        之所以会这么问,是刚好有个后台管理的页面,宽度100%的,也有类似的滚动条有无时候页面跳动的问题,所以想这么用一下。
        我觉得针对这种问题,可能最好的方案就是垂直滚动条始终显示。

  31. 大LOVE辉说道:

    水平滚动条跳动问题修复
    这里确定是padding-left,而不是padding-right吗

  32. 葛古月说道:

    你的方法我试了下,特定的情况的确可行,可是有些情况下,就没有效果了;
    我一直用的方法都是直接在body加上width:100vw就解决任何情况下这个跳动的问题了,我不知道是不是我这个方法有什么缺陷,还是什么,就是没人用过这方法,所以咨询你一下

  33. vsou说道:

    看到有人抄你的文章,而且,图片都不带换的,呵呵哒http://blogread.cn/it/article/7377?f=wb

  34. 杨宗军说道:

    张大大,您好,刚看了这个例子,但我的总是达不到这个效果,不知道怎么回事
    然后效果http://codepen.io/bmxklYzj/pen/VabqEv

    • clg说道:

      这个在最外面加一层。
      然后把样式加在wrap上面就行。

      .wrap {padding-left: calc(100vw – 100%);}
      或者
      .wrap {margin-left: calc(100vw – 100%);}

  35. 芒果说道:

    为什么在demo中给main加上margin-left: calc(100vw – 100%);还是会跳动呢?

  36. mask说道:

    margin-left: -webkit-calc(100vw – 100%);
    margin-left: calc(100vw – 100%);

    原理:vw是浏览器视口的宽度,包括滚动条,100%是不包括滚动条的视口宽度。
    这样如果有滚动条,两者之差就是滚动条的宽度;margin-left一个向左滚动条的宽度,让两边都有一个滚动条宽度。
    如果没有滚动条,两者之差就是0

    优点:纯css3实现,简洁高效。
    缺点:IE10以下无效,设计图如果有背景会存在问题,因为有margin-left存在。

  37. 大怪兽说道:

    LZ写多一个适用浏览器或者浏览器实测就会更加好了!

  38. 2hu说道:

    mac 下貌似滚动条不会影响页面的宽度 发现标题也没有出现跳动 safari 和chrome 都看过了

  39. 酷我说道:

    GG浏览器 依然抖动中

  40. csuft说道:

    美女还是抖动了,浏览器为360版本7.1.1.556,内核版本31.0

  41. 连培培说道:

    不晃动的文字完全被遮挡,根本看不到是否跳动了.

  42. lluvio说道:

    刚好用到~谢谢张老师

  43. 说道:

    demo里还是抖动了呀
    chrome33 linux

    1366*768

  44. 姬丝秀忒·雅赛劳拉莉昂·刃下心说道:

    感人,哈哈

  45. 大冒险家说道:

    感觉应该是 margin-right: calc(100vw – 100%);

  46. 大冒险家说道:

    感觉有问题啊,demo里还是有偏移的

  47. 于江水说道:

    自荐一篇文章,补充 JS 来检测滚动条宽度避免页面偏振的方法: http://yujiangshui.com/review-how-to-make-popup-mask-effect/#去掉滚动条但是避免页面内容偏移