前端开发者学堂 - fedev.cn

Web技巧(15)

发布于 大漠

上一期咱们围绕着Web动画展开,其中有的动画对部分用户群体会造成不良的反应,会引起癫痫。为此,为了避免这种现象出现,可以使用条件CSS@media中的prefers-reduced-motion条件来做处理。除此之外,prefers-reduced-motion<picture>元素中还有一些小技巧,可以帮我们做一些其他有意义的事情。这一期,就从这个特性开始讲起。

prefers-reduced-motion<picture>的结合

<picture>是HTML5的一个元素,可以使用该元素来较好的实现响应式图片。另外,HTML的<img>元素的srcsetsizes属性也能实现类似的效果。

如果你对Web中图像相关的知识感兴趣的话,可以花点时间阅读下面相关的文章:

回到prefers-reduced-motion<picture>上来。

当用户系统中开启减弱动态效果之后,我们就可以通过媒体查询@mediaprefers-reduced-motion来减弱动态效果,即元素直接不启用任何动效

/* 开启 减弱动态效果 的设备会禁用 aniName动画 */ 
@media screen and (prefers-reduced-motion) { 
    / * 禁用不必要的动画 * / 
    .element { 
        animation: none; 
    } 
}

上面对于CSS控制的动效可以得到很好的降级处理,但Web页面会启用一些.gif动态图片。那么对于使用媒体查询就不好处理。不过,值得庆幸的是,HTML5的<picture>可以基于source中的media值来对动态图做一些降级处理。当然,在media中也同样需要基于prefers-reduced-motion的取值。比如下面这个示例:

<picture>
    <source srcset="static.png" media="(prefers-reduced-motion: reduce)" />
    <img srcset="animated.gif" alt="animated image" />
</picture>

就上面的示例而言,在<picture>中的<img>元素引入了默认的动态图片,然后在<source>中引入降级处理的图片。一旦用户系统启用了“减弱动态效果”:

Web终端(比如Safari浏览器)就会启用降级图片static.png

虽然.gif动图可以让视觉效果动起来(Web动效中模式之一),但.gif文件的引用在性能上是有所制约,特别是在移动终端上。不过,除了.gif动图之外,还可以使用.mp4这样的视频文件也能让Web视觉效果动起来,而且视频要比.gif图性能好得多。事实上,有不少同学开始.gif文件转换成视频文件,然后再将其运用到Web中。

那么我们就可以在<picture><source>中引入一个.mp4文件。只不过这样我们就需要三个源媒体文件:

  • 当开启“减弱动态效果”,会启用非动态图片,比如static.png
  • 同样在<img>中引用一个动态图片做为默认资源,比如animated.gif
  • 如果识别<picture>中引用的资源,而且未开启“减弱动态效果”,则会启用视频文件来替代.gif图,比如animated.mp4

例如:

<picture>
    <source srcset="static.png" media="(prefers-reduced-motion: reduce)" />
    <source srcset="animated.mp4" type="video/mp4" />
    <img srcset="animated.gif" alt="animated image" />
</picture>

就上例,在不同的浏览器会加载不同的源媒体:

虽然Firefox也支持<picture>元素,但它似乎不能正常工作,加载的依旧是animated.gif文件。具体原因不名,有可能是Firefox对<source>的支持还有一定的缺陷。

有关于这方面更详细的介绍可以阅读@Chris Coyier的《Reduced Motion Picture Technique, Take Two》一文。

Flexbox 和 Grid 容器中的伪元素

熟悉FlexboxGrid布局的同学都应该知道。如果在元素上显式的设置display的值为flex/inline-flexgrid/inline-grid就会创建Flex容器或Grid容器,同时就创建了FFC(Flexbox Formatting Context)或GFC(Grid Formatting Context)。那么Flex容器或Grid容器的子元素就自动成为Flex项目或Grid项目。同时相应的Flex项目属性或Grid项目属性就可以运用到这些元素之上。

在Flex容器或Grid容器上可以使用伪元素::before::after。运用于容器的伪元素在很大程度上就像一个子元素,因此也自动会变成Flex项目或Grid项目。

Flex容器或Grid容器中的伪元素::before::after看上去像一个子元素。不过有一个棘手的问题,除了用于创建它的选择器之外,没有其他选择器可以选中它。

ul::before {
    content: 'x';
    display: inline-flex;
    justify-content: center;
    align-items: center;
    min-height: 10vh;
    background: #f36;
    margin: 5px;
    color: #fff;
}

或许你会想到,既然伪元素看上去像一个子元素,那么结构性选择器,比如:nth-child():nth-last-child()可以选中。事实是不可以的,如果伪元素和子元素完全一样,将会影响这些选择器。

ul > :nth-child(1) {
    background: #f90;
    border:2px solid #09f;
}

ul > :nth-last-child(2) {
    background: #09f;
    border:2px solid #f90;
}

另外一个问题就是,在JavaScript中不能像选择常规子元素那样选择伪元素。比如,document.querySelector('.flex::before')将返回一个null。如果你想在JavaScript选中伪元素以及想看看它的样式规则,可以使用CSSOM中的相关特性来获取到:

const styles = window.getComputedStyle(
    document.querySelector('.flex'), '::before'
)

console.log(styles.content)                     // ❯ "x"
console.log(styles.color)                       // ❯ rgb(255, 255, 255)
console.log(styles.getPropertyValue('color'))   // ❯ rgb(255, 255, 255)

基于CSS自定义属性和em单位的吶应式布局

响应式布局中会根据不同的终端屏幕设计不同的字号、间距等。在以前的相关教程介绍了精准流体布局的方法:

其实他们的原理非常简单,利用视窗单位vw等和calc()函数来计算font-size或者padding等属性的值。

@guerriero_se在的新博文中介绍了另一种实现响应式布局的方法,即基于CSS自定义属性em单位

主要分为两部分,一部分是对font-sizeline-height相关的设计(有关于文字排版);另一部分就是有关于间距的设计。

响应式排版

为了控制文本相关的缩放,定义了两个自定义属性:--text-base-size--text-scale-ratio。第一个是body元素的font-size;第二个是用于缩放的比例。而且--text-base-size的默认值是1em

:root {
    // body font size
    --text-base-size: 1em;
    
    // type scale
    --text-scale-ratio: 1.2;
    --text-xs: calc((1em / var(--text-scale-ratio)) / var(--text-scale-ratio));
    --text-sm: calc(var(--text-xs) * var(--text-scale-ratio));
    --text-md: calc(var(--text-sm) * var(--text-scale-ratio) * var(--text-scale-ratio));
    --text-lg: calc(var(--text-md) * var(--text-scale-ratio));
    --text-xl: calc(var(--text-lg) * var(--text-scale-ratio));
    --text-xxl: calc(var(--text-xl) * var(--text-scale-ratio));
    --text-xxxl: calc(var(--text-xxl) * var(--text-scale-ratio));

    // line-height
    --body-line-height: 1.4;
    --heading-line-height: 1.2;

    // capital letters - used in combo with the lhCrop mixin
    --font-primary-capital-letter: 1;
}

注意,在定义每种文本大小类型的自定义属性时,使用1em乘以--text-scale-ratio1em不是基于--text-base-size的值。你可以根据自己的需要设置--text-base-size的值不等于1em。由于em单位是基于当前font-size的大小计算的,如果我们在不同的媒体查询中更新--text-base-size的值,那么就会更新bodyfont-size,并使用级联效应,更新所有文本大小自定义属性。这样一来,整个排版都会受到影响。

@supports(--css: variables) {
    :root {
        @include breakpoint(md) {
            --text-base-size: 1.25em;
        }
    }
}

响应式间距

响应式间距和响应式排版有点类似:

:root {
    --space-unit:  1em;
    --space-xxxxs: calc(0.125 * var(--space-unit));
    --space-xxxs:  calc(0.25 * var(--space-unit));
    --space-xxs:   calc(0.375 * var(--space-unit));
    --space-xs:    calc(0.5 * var(--space-unit));
    --space-sm:    calc(0.75 * var(--space-unit));
    --space-md:    calc(1.25 * var(--space-unit));
    --space-lg:    calc(2 * var(--space-unit));
    --space-xl:    calc(3.25 * var(--space-unit));
    --space-xxl:   calc(5.25 * var(--space-unit));
    --space-xxxl:  calc(8.5 * var(--space-unit));
    --space-xxxxl: calc(13.75 * var(--space-unit));
}

设置了一个自定义属性--space-unit的值为1em,而且模块化缩放比例是基于斐波那契数列(Fibonacci sequence)。同样的,在不同的媒体查询中,--space-unit可以设置不同的的值:

@supports(--css: variables) {
    :root {
        @include breakpoint(md) {
            --space-unit:  1.25em;
        }
    }
}

有关于更详细的代码,可以查阅读CodyHouse Framework

图解JavaScript的map()filter()reduce()

JavaScript中有关于数组的API有很多:

其中有关于map()filter()reduce()的几个方法,有很多非常形象的图来阐述,比如@Una Kravets的手绘图

@JavaScript Teacher还为这几个API设计了相应的动态图

有关于这方面更多的介绍,可以阅读:

小结

在这一期中主要围绕三个部分展开,其一介绍了prefers-reduced-motion媒体查询条件和HTML5的<picture><source>结合在一起对动态图片做降级处理,或者说在最适合的环境加载最适合的源媒体;其二介绍了如何使用CSS自定义属性和em单位实现响应式布局的另一种方法;其三介绍了Flexbox和Grid容器中的伪元素会生成相对应的项目,具备Flex项目、Grid项目的属性,不过他们看上去类似子元素,但结构性选择器无法运用到其身上,如果使用JavaScript来操作伪元素的样式的话,需要借助CSSOM等特性。最后用收集了不同的图来阐述JavaScript的mapfilterreduce等API特性。