改变用户体验的滚动新特性

注意事项:
如支付后未自动显示完整内容,可点击“已支付?点此查询订单”进行查看。
如遇内容不符或缺失,请联系内容作者或平台客服(工作日 9:00-18:00)。

@evilmartians的《滚动的特性》一文介绍了目前有关于滚动相关的特性。今天我想花点时间重新整理一下,时至今日,CSS中为浏览器滚动提供的相关新特性究竟能给用户带来哪些新的体验。

墨守成规的滚动条

一直以来,如果仅使用CSS来控制滚动条,我们只能借用overflow属性,比如:

overflow: auto | scroll;

// 或者
overflow-x: auto | scroll;

// 或者
overflow-y: auto | scroll;

当元素内容溢出容器之后,就会出现滚动条,其滚动效果如下:

这样的效果大家或许已经习惯,觉得页面或者元素滚动的效果就应该如此。甚至在Modal弹框中的滚动效果,大家觉得也应该是如此:

除了滚动体验之外,视觉体验相对而言更为糟糕,不同系统的浏览器渲染的滚动条风格也不是一致:

对于大部分同学而言,这些东西就应该是如此,不应该也不会有太多的变化。但对于有追求的设计师或者工程师来说,还是不想墨守成规,想给用户带来不一样的体验,不管是视觉外观上,还是滚动流畅性。正因为如此,早期有很多优秀的JavaScript库来改变这一切。

随着技术的革新,就在现在或者未来的不久,我们可以采用纯CSS的一些特性来改变这一切,让用户有一个更好的体验。

改变滚动条外观效果

前面提到过了,滚动条外观的效果在不同系统存在不一样的效果已是事实。虽然在Github有上百个库可以让开发者实现个性化的滚动外观,但对于CSSer而言,能用纯CSS解决的问题就决不使用JavaScript来解决。

在Webkit内核提供了-webkit-scrollbar(由七个伪元素)属性,可以轻易的帮助我们实现自定义(个性化)滚动条UI风格。在介绍这七个伪元素属性之前,先来看一下滚动条的结构:

仅从上图来看,是不是有一种似曾相识。是的,它的结构和我们平时看见的进度条或input[type="range"]类似:

也就是说,我们可以像制作进度条一样来处理滚动条UI效果。不同的是采用的CSS属性不一样。刚才也提到了,-webkit-scrollbar提供了七个伪元素,通过这些伪元素,我们可以来定制滚动条外观效果。这七个伪元素分别是:

  • ::-webkit-scrollbar:整个滚动条
  • ::-webkit-scrollbar-button:滚动条上的按钮(下下箭头)
  • ::-webkit-scrollbar-thumb:滚动条上的滚动滑块
  • ::-webkit-scrollbar-track:滚动条轨道
  • ::-webkit-scrollbar-track-piece:滚动条没有滑块的轨道部分
  • ::-webkit-scrollbar-corner:当同时有垂直和水平滚动条时交汇的部分
  • ::-webkit-resizer:某些元素的交汇部分的部分样式(类似textarea的可拖动按钮)

具体使用的时候非常的简单。HTML结构和我们平时是一样的:

<div class="scrollbar">
    <div class="force-overflow"></div>
</div>

有关于滚动条的UI样式风格都在.scrollbar上设置,比如:

.scrollbar {}
.scrollbar::-webkit-scrollbar{}
.scrollbar::-webkit-scrollbar-thum{}
.scrollbar::-webkit-scrollbar-track{}

在对应的-webkit-scrollbar属性写上你想要的UI样式风格,你就可以得到对应的滚动条UI样式风格,比如下图这样的:

具体的代码可以查看@akinjideCodepen上写的Demo

有关于采用-webkit-scrollbar属性实现自定义滚动条UI效果的详细教程,可以阅读@Akinjide Bankole教程。其他教程也可以查看下面的文章:

如果你不想烧脑,也可以使用在线生成器,比如@Darryl Huffman写的Scrollbar Generator:

虽然能用纯CSS搞定这一切,肯定也有不少同学会好奇,现在能否用于实际项目当中。还是来看看浏览器对其支持性吧:

顠红的较少了,或许大家更为安心了。如果你想做得更好,那么可以借助在《五个最新的CSS特性以及如何使用它们》文中介绍的@supports特性来做降级处理。如果浏览器支持-webkit-scrollbar,那么采用自定义属性,如果不支持,则采用默认的滚动条风格。是不是很完美。

丝滑般的滚动

视差滚动效果,大家应该不会感到陌生吧:

视差效果曾经很多品牌网站都可见。有关于什么是视差滚动效果,这里不做详细的阐述,如果你从未接触过或者想了解如何制作视差滚动效果,建议你花点时间阅读下面两篇文章:

同样的,早期也涌现很多优秀的JavaScript库来实现视差滚动效果。只不过这里我们不是着重介绍怎么制作视差滚动效果。而是来聊聊,怎么通过使用CSS提供的滚动特性,实现丝滑般的滚动效果。而视差滚动效果是一个很好的示例。

先来看两个效果,一个是普通的滚动效果,另一个是采用了新特性的滚动效果:

普通滚动效果

普通滚动效果,大家常见的滚动效果

优化后的滚动效果

怎么实现后者这样丝滑般的滚动效果,才是接下来的目的。很多同学可能首先会想到jQuery.scrollTo方法或者类似的解决方法。但jQuery已经慢慢的淡出公众眼线。

不过也有可能立马想到原生的JavaScript,比如window.scrollTo(x, y)方法。更优秀的程序员可能会借助window.setTimeout()window.setInterval()Web Animation APIwindow.requestAnimationFrame()让滚动效果更为平滑,也就是我们想要给用户丝滑般的滚动体验。比@pawelgrzybek提供的这段代码:

function scrollIt(destination, duration = 200, easing = 'linear', callback) {

    const easings = {
        linear(t) {
            return t;
        },
        easeInQuad(t) {
            return t * t;
        },
        easeOutQuad(t) {
            return t * (2 - t);
        },
        easeInOutQuad(t) {
            return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
        },
        easeInCubic(t) {
            return t * t * t;
        },
        easeOutCubic(t) {
            return (--t) * t * t + 1;
        },
        easeInOutCubic(t) {
            return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
        },
        easeInQuart(t) {
            return t * t * t * t;
        },
        easeOutQuart(t) {
            return 1 - (--t) * t * t * t;
        },
        easeInOutQuart(t) {
            return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t;
        },
        easeInQuint(t) {
            return t * t * t * t * t;
        },
        easeOutQuint(t) {
            return 1 + (--t) * t * t * t * t;
        },
        easeInOutQuint(t) {
            return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t;
        }
    };

    const start = window.pageYOffset;
    const startTime = 'now' in window.performance ? performance.now() : new Date().getTime();

    const documentHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);
    const windowHeight = window.innerHeight || document.documentElement.clientHeight || document.getElementsByTagName('body')[0].clientHeight;
    const destinationOffset = typeof destination === 'number' ? destination : destination.offsetTop;
    const destinationOffsetToScroll = Math.round(documentHeight - destinationOffset < windowHeight ? documentHeight - windowHeight : destinationOffset);

    if ('requestAnimationFrame' in window === false) {
        window.scroll(0, destinationOffsetToScroll);
            if (callback) {
            callback();
        }
        return;
    }

    function scroll() {
        const now = 'now' in window.performance ? performance.now() : new Date().getTime();
        const time = Math.min(1, ((now - startTime) / duration));
        const timeFunction = easings[easing](time);
        window.scroll(0, Math.ceil((timeFunction * (destinationOffsetToScroll - start)) + start));

        if (window.pageYOffset === destinationOffsetToScroll) {
            if (callback) {
                callback();
            }
            return;
        }

        requestAnimationFrame(scroll);
    }

    scroll();
}

调用方式很简单,像下面这样即可:

document.querySelector('.js-btn1').addEventListener('click', () => {
    scrollIt(
        document.querySelector('.js-section1'),
        300,
        'easeOutQuad',
        () => console.log(`Just finished scrolling to ${window.pageYOffset}px`)
 
剩余50%内容付费后可查看
看完了?还不过瘾?点此向作者提问