• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

Sticky Headers

CSS3 web前端中文站 2年前 (2017-07-09) 951次浏览 已收录 0个评论

Sticky Header

在实际业务中经常碰到页头固定在浏览器的顶部,而在移动端上使用position:fixed坑多难搞。记得 EFE 团队分享过一篇《Web 移动端 Fixed 布局的解决方案》博文,就是介绍如何解决移动端上实现页头固定的技术方案。除了文章中介绍的方案之外,@Brad Frost 也推荐了几个 JavaScript 的解决方案,比如 iScroll 4 和 Scrollability。使用fixed是一种固定页头的,但很多时候是希望实现Sticky Header的效果,说到这里大家可能会想起position新增的属性值sticky。虽然这个能实现我们想要的效果,但这个属性的支持性还是需要等待一段时间。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

sticky 正常的使用方法

position:sticky正常的使用方法,非常的简单:

<div class="header">Sticky Headers</div>  .sticky {     position: sticky;     top: 15px; } 

元素sticky距离浏览器顶部15px,该元素就固定在那了。很多时候这个配合 JavaScript 的scroll事件一起使用。

var header = document.querySelector('.header'); var origOffsetY = header.offsetTop;  function onScroll(e) {     window.scrollY >= origOffsetY ? header.classList.add('sticky') : header.classList.remove('sticky'); }  document.addEventListener('scroll', onScroll, false); 

看上去是不是非常的简单。刚才也说了,sticky的支持度还是需要等待一段时间。可以通过 caniuse.com 来查阅。

有关于position:sticky的相关资源也可以阅读下面几篇文章:

  • Specification
  • geddski article: Examples and Gotchas
  • HTML5Rocks
  • Mozilla Developer Network (MDN) documentation – CSS position
  • WebPlatform Docs
  • IE platform status: Preview Release
  • Chrome platform status:
  • WebKit platform status: Supported

当然也有对应的 Polyfill。比如这个和这个。不过@Jeff Wainwright 前几天在 CSS-Tricks 网站上分享了一篇文章使用Stickybits来替换position:sticky的 Polyfills 方案:

Sticky Headers

如果对 Stickybits 感兴趣的同学,可以仔细阅读这篇文章《Stickybits: an alternative to position: sticky polyfills》或其官网查阅相关文档。

虽然上面的方案都可以解决Sticky Headers效果,但我更对@Remy Sharp 分享的几篇文章更感兴趣:

  • Sticky headers (#1/3)
  • Smooth scroll & sticky navigation (#2/3)
  • CSS sticky nav & smooth scroll (#3/3)

下面的内容是根据上面三篇文章整理而来的,不过英文好的同学建议直接阅读上面三篇文章。

Sticky Headers

以前实现 Sticky Headers 效果,很多时候都是借助于 JS(更早有很多 jQuery 插件)。比如像下面这样的代码:

var toggleHeaderFloating = function() {     // Floating Header     if ($window.scrollTop() > 80) {         $('.header-section').addClass('floating');     } else {         $('.header-section').removeClass('floating');     } }  $window.on('scroll', toggleHeaderFloating); 

上面的代码检查每次浏览器垂直滚动条滚动的位置超过80px的时候给元素.header-section添加floating类名,反之则删除floating类名。

其中$window是一个window对象。但事实上,这段代码有很多禁忌。不过这不是一个大问题,问题是我们应该要理解如何避免一些小障碍。

几年前@Paul Irish 分享过一篇滚动性能相关的文章,文章虽然介绍的是scroll事件,但也可能适用于wheelmousemove事件。文章中建议使用scroll事件时应该尽量避免接触 DOM(Touching DOM),避免引发布局(也称为回流)。@Paul 也搜集了一些,怎样才会触发回流。

如果我们在scroll事件上什么都不做,我们又能做些什么呢?我们可以使用requestAnimationFrame以防反跳(Debounce)。当用户滚动滚动条时,我们会使用一个函数来检查滚动条的位置,但如果用户快速滚动滚动条,那么我们最好先避免Scroll Jank

// used to only run on raf call var rafTimer; $window.on('scroll', function(){     cancelAnimationFrame(rafTimer);     rafTimer = requestAnimationFrame(toggleHeaderFloating); }); 

上面的 jQuery 代码有两件事情一直困扰我:

  • 每次scroll事件都会触发运行一个 jQuery 选择器
  • 查询每一个元素

诚然getElementsByClassName(jQuery 或 Sizzle 选择一个类)已经优化得很好,这不是很大的问题。然而,我们在每一个滚动勾子时不需要构造一个新的 jQuery 对象。

在 Chrome Devtools 中运行下面的代码,每次滚动页面将会记录运行的次数:

window.onscroll = () => console.count('scroll') // or monitorEvents('scroll') 

Sticky Headers

整体代码:

var $headerSection = $('.header-section'); var toggleHeaderFloating = function () {     // Float Header     if ($window.scrollTop() > 80) {         $headerSection.addClass('floating');     } else {         $headerSection.removeClass('floating');     } }  var rafTimer; $window.on('scroll', function(){     cancelAnimationFrame(rafTimer);     rafTimer = requestAnimationFrame(toggleHeaderFloating); }); 

事实上,当滚动条位置超过一定的阈值时,header-section只需要改变一个场景。

另一种选择是使用classList检查这个类是否需要改变。

var rafTimer; window.onscroll = function (event) {     cancelAnimationFrame(rafTimer);     rafTimer = requestAnimationFrame(toggleHeaderFloating); }  function toggleHeaderFloating() {     // does cause layout/reflow: https://git.io/vQCMn     if (window.scrollY > 80) {         document.body.classList.add('sticky');     } else {         document.body.classList.remove('sticky');     } } 

组件问题

预期的效果是,当我滚动滚动条时,导航会固定在页头。哪果我点击导航菜单项时,将平滑地滚动到对应的位置。

Sticky Headers

实际效果,你可以点浏览 2016.ffconf.org 网站查看效果。

要实现此效果,还有些问题有待解决:

Sticky Element

Sticky Element 元素是导航部分,它一直固定在页面的顶部会更简单,因为它总是有一个position:fixed样式设置。虽然,导航元素只有超过一个阈值才会粘在顶部。

最初考虑我们是否能使用IntersectionObserver,一种逆向方法,但它不适合。

下面的代码跟踪和应用sticky类名来解决导航元素的位置。请注意,我把sticky类名用在body元素上。至于为什么,稍后再聊。

// 获取 Sticky Element,这里指的是`sticky-header`元素 var stickyHeader = document.getElementById('sticky-header');  // 记录当前的位置,当超过这个阈值时添加`sticky`类名,反则删除 var boundary = stickyHeaderRef.offsetHeight;  // 当页面滚动时,尽可能少,在这种情况下注册一个 rAF 回调`checkSticky` window.onscroll = function (event) {     requestAnimationFrame(checkSticky); }  function checkSticky() {     // 收集当前滚动条位置     var y = window.scrollY + 2;      // 检测 body 元素是否包含`sticky`类     var isSticky = document.body.classList.contains('sticky');      if (y > boundary) {         // 当前滚动条位置超过阈值         // body 元素并没有`sticky`类; 如果没有包含,添加该类名         if(!isSticky) {             document.body.classList.add('sticky');         }     } else if (isSticky) {         document.body.classList.remove('sticky');     } } 

只有上面的 JavaScript 代码还是不够的,还需要配合一些 CSS 代码:

#sticky-header {     top: 0; } body.sticky {     padding-top: 100px; } body.stick #sticky-header {     position: fixed; } 

对应的原理就不用做更多的阐述吧。这里有两个点比较重要,当滚动条位置超过预定阈值时,body元素会添加一个sticky类名,并且给body.sticky添加padding-top:100px。同时 Sticky 元素的position:static变成了position:fixed。给body添加一个padding-top:100px主要是让内容不会被 Sticky 元素固定在顶部是遮住内容。

链接到锚点位置

这里指的是,当你点击导航栏的菜单项时,到达到页面的指定位置。也就是对应的锚点位置。

现在我们实现了导航粘在浏览器顶部,但我们单击导航中链接时页面会跳转,但导航元素遮盖了标题,这不是我们想要的效果:

Sticky Headers

为了解决这个问题,给目标元素添加一个height值,来抵消导航元素的高度,在我们的示例中是100px

:target:before {     content: '';     display: block;     height: 100px; } 

这里采用了 CSS 选择器:target和伪类选择器:before配合。

平滑滚动

另一个有待解决的问题是,当我们点击导航到达指定位置时需要一个平滑滚动效果。这个让事情变得有点复杂。不过我发现一个较好的 JavaScript 库,但发现的有点晚。

虽然用已有的 JavaScript 库来解决这个效果,但我还是决定自己来强撸这个功能。部分功能是我所能预料到的,但部分功能是我想得太简单了。比如说,我想用一个简单的 Tweening 函数,但我自己还是不能独立完成,最后还是选择了@Soledad Penadés 的 tween 库。

具体代码如下,代码中有一些简单的注释:

// 监听 body 元素的点击事件 document.body.addEventListener('click', function(event){     var node = event.target;     var location = window.location;      // ignore non-links elements being clicked     if (node.nodeName !== 'A') {         return;     }      // ignore cmd+click etc     if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) {         return;     }      // only hook local URLs to the page     if (node.origin !== location.origin || node.pathname !== location.pathname){         return;     }      event.preventDefault();      window.history.pushState(null, null, node.hash);      var target = document.querySelector(node.hash);      var fromY = window.scrollY;     var coords = {         x: 0,         y: fromY     }     var y = target.offsetTop;      if (fromY < y) {         y -= 100; // offset for the padding-top     }      var running = true;      var tween = new TWEEN.Tween(coords)         .to({x: 0, y: y}, 500)         .easing(TWEEN.Easing.Quadratic.Out)         .onUpdate(function(){             window.scrollTo(this.x, this.y);              if (this.y === y) {                 running = false;             }         })         .start();      requestAnimationFrame(animate);      function animate(time) {         if (running) {             requestAnimationFrame(animate);             TWEEN.update(time)         }     } }); 

原生的 CSS 方案

第二部分主要介绍的是使用 JavaScript 来实现 Sticky 元素和平滑滚动的效果。但随着 CSS 发展,可以使用 CSS 来实现。

html {     scroll-behavior: smooth; }  #masthead {     position: sticky;     top: calc(-100% + 100px);     /* make sure stick above images */     z-index: 1;      /* tweaks to the ffconf design     to keep the height right */     display: flex;     flex-direction: column; }  .logo-wrapper {     flex-basis: 85vh; } 

具体的 DEMO 可以点击这里查看效果。

遗憾的是,到目前为止仅只有 Firefox 浏览器同时支持position:stickyscroll-behavior。但在其他的浏览器中也能看到较好的效果,比如 Chrome 浏览器。

回到最初,如果你在项目中想直接使用position:stickyscroll-behavior。你可以在支持的浏览器中直接使用这两个属性,在不支持的浏览器中使用对应的 Polyfill。

  • Sticky 的 Polyfill:Stickybits
  • scroll-behavior 的 Polyfill:Smooth Scroll Polyfill

总结

这篇文章简单的介绍了如何在项目中实现positon:sticky和平滑滚动条效果。除了借助 JavaScript 之外,我们更期待使用纯 CSS 来实现。我们的宗旨是:能使用 CSS 实现的效果绝不使用 JavaScript。但碍于浏览器对其兼容性的原因,在实际项目中,可以考虑采用对应的 Polyfill。这个时候,你可能会说,这样一来还不是使用 JavaScript 吗?事实是这样,但很多时候,咱们还可以考虑 Houdini 来实现。如果你对 Houdini 从未了解过,建议阅读@Philip Walton 在 2017 CSS Day 分享的主题《Houdini & Polyfilling CSS》。这是一个很有意思的话题。

【注:本文源自网络文章资源,由站长整理发布】

Sticky Headers

大漠

常用昵称“大漠”,W3CPlus 创始人,目前就职于手淘。对 HTML5、CSS3 和 Sass 等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对 CSS3 的研究,是国内最早研究和使用 CSS3 技术的一批人。CSS3、Sass 和 Drupal 中国布道者。2014 年出版《图解 CSS3:核心技术与案例实战》。

web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Sticky Headers
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址