Safari 26 来了,那些让页面布局和动效更简单的 CSS 新特性,到底该怎么用?

4,307字
18–27 分钟
in

最近苹果家的 Safari 浏览器悄咪咪更新到了 26 版本,本来嘛,浏览器发新版本就跟手机 App 更新一样稀松平常,一般就是修修补补加几个小功能。但这次还真有点不一样,官方博客里一翻,好家伙,新增了 75 个新特性,还有 171 项改进。这波操作简直可以称之为“史诗级”更新,尤其是 CSS 这一块,冒出了不少以前只能在 Chrome 里眼馋的功能。今天咱就盘一盘,这几个能实实在在解决开发痛点的新玩意儿到底怎么上手玩起来。

目录

锚点定位

基础绑定

把两个在 HTML 里八竿子打不着的元素强行“粘”在一起,这事儿以前得靠复杂的 JavaScript 计算位置,或者用 CSS 的各种 hack 才能勉强实现。现在有了 CSS 锚点定位,这事儿就变得跟吃薯条蘸番茄酱一样简单。

咱们先弄个例子,页面上有两个 div,一个叫“锚点”,另一个叫“目标”,它俩在代码里是前后脚出现的兄弟关系。想让目标元素死死地贴在锚点元素的旁边,不管屏幕怎么缩放,它俩都像连体婴一样。

/* 给锚点元素起个独一无二的代号 */
.anchor-element {
  anchor-name: --special-anchor;
}

/* 目标元素开始它的表演 */
.target-element {
  position: absolute;
  position-anchor: --special-anchor;
}

只要给锚点元素写上 anchor-name 属性,起个以双减号开头的名字,再给目标元素写上 position-anchor 指向这个名字,目标元素就会立刻被“吸附”到锚点元素的正中央。即便这俩元素在 HTML 结构里隔了十万八千里,也丝毫不影响它俩的“感情”。

这里得提一嘴,虽然 CSS 让它们在视觉上绑定了,但在屏幕阅读器这类辅助技术眼里,它俩可能还是陌生人。所以最好用 aria-describedby 或者 aria-labelledby 给它们牵个线,这样用读屏软件的小伙伴才能明白这俩玩意儿是一伙的。

位置微调

光吸附到中心肯定不够,咱得把它挪到想要的位置,比如右上角。这时候 position-area 属性就派上用场了。

.target-element {
  position: absolute;
  position-anchor: --special-anchor;
  position-area: top right;
}

position-area 的工作原理特别像在锚点元素周围画了个九宫格,通过指定格子的名称,就能把目标元素扔到对应格子里。比如 top right,就是把目标元素放到锚点元素的右上角那一格。这个属性让元素的定位变得像玩游戏一样直观。

滚动驱动动画

阅读进度条

以前做个滚动条跟着页面走的效果,得监听滚动事件,计算滚动百分比,再动态修改元素的宽度,代码写起来又臭又长。现在用 scroll() 函数,几行 CSS 就能搞定一个丝滑的阅读进度条。

先定义一个简单的动画,让元素从宽度为 0 变到完整宽度。

@keyframes fillWidth {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

.progress-indicator {
  transform-origin: left center;
  animation: fillWidth linear;
}

最关键的一步来了,把动画和滚动条绑定在一起。

.progress-indicator {
  transform-origin: left center;
  animation: fillWidth linear;
  animation-timeline: scroll();
}

加上 animation-timeline: scroll() 之后,这个动画就不再是按时间跑了,而是跟着页面滚动的进度走。页面滚到底,进度条动画也就刚好播放完,完全不需要 JavaScript 插手。而且 transform-origin: left center 保证了进度条是从左往右增长的,看着贼舒服。

视差入场

想让图片在滚动到可视区的时候,带着点淡入淡出或者上浮的效果,这效果以前得用 Intersection Observer 这类的 API 配合一堆逻辑才能实现。现在 view() 函数配合 animation-range 就能轻松搞定。

给所有图片绑一个淡入上浮的动画。

@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

img {
  animation: fadeUp linear;
  animation-timeline: view();
}

这时候刷新页面会发现,动画在图片刚进入视野的时候就开始了,但还没播放完,图片可能就滚出屏幕了,效果大打折扣。这时候就需要 animation-range 来控制动画的播放区间。

img {
  animation: fadeUp linear;
  animation-timeline: view();
  animation-range: 0% 50%;
}

设置 animation-range: 0% 50%,意思就是当图片底部刚刚进入可视区底部时(0%),动画开始;当图片顶部滚动到可视区中间时(50%),动画结束。这样就能确保整个动画过程都在用户视线范围内完美呈现。animation-range 的值可以根据实际效果微调,比如想让动画结束得早一点就调低百分比,结束得晚一点就调高。

progress() 函数

响应式透明度变化

以前想在屏幕变宽的时候让图片慢慢变淡,得用 calc() 配合百分比计算,但 calc() 在处理不同单位混搭的时候经常翻车。progress() 函数简直就是为这类场景量身定制的,它能轻松算出某个值在指定区间内的进度。

假设想让图片的透明度随着视口宽度变化,视口宽度在 400px 时完全不透明(1),到 1000px 时透明度降到最低(0.25)。代码可以这么写:

.responsive-image {
  opacity: clamp(0.25, progress(100vw, 400px, 1000px), 1);
}

progress(100vw, 400px, 1000px) 返回的是当前视口宽度在 400px 到 1000px 这个区间内的进度。如果视口宽度小于 400px,函数返回 0;大于 1000px,返回 1。返回的值可能是个小数,比如 0.5。再配合 clamp() 函数,把值限制在 0.25 到 1 之间,就能实现从完全不透明到 0.25 透明度的平滑过渡。

这里有个小细节需要注意,虽然规范上说 progress() 的值会自动 clamp 在 0 和 1 之间,但实际测试发现有时候不 clamp,所以手动加个 clamp() 更保险,避免意外情况发生。

多属性联动

progress() 的魅力在于它返回的是一个数值,这个数值可以被其他属性使用,实现多属性联动。比如不仅透明度变化,还能同时改变模糊程度或者尺寸。

.fancy-element {
  --view-progress: progress(100vw, 400px, 1000px);
  opacity: clamp(0.25, var(--view-progress), 1);
  filter: blur(calc(var(--view-progress) * 10px));
  transform: scale(calc(0.8 + var(--view-progress) * 0.2));
}

progress() 的返回值存成一个 CSS 变量,然后在多个地方复用这个变量,就能让元素的透明度、模糊程度、缩放大小全部根据屏幕宽度同步变化。这种细腻的响应式交互,以前得写一大坨 JavaScript,现在几行 CSS 就搞定了。

绝对定位自对齐

居中大法

想要把一个绝对定位的元素完美居中,老办法得设置 top: 50%; left: 50%; 再配合 transform: translate(-50%, -50%);,这招虽然管用,但总觉得不够优雅。Safari 26 里,justify-selfalign-self 在绝对定位元素上也能用了,居中这事儿瞬间清爽了不少。

.center-modal {
  position: absolute;
  justify-self: center;
  align-self: anchor-center;
}

如果只想在水平方向居中,justify-self: center 就搞定。垂直方向想居中,按理说 align-self: center 应该行,但它会把元素相对于自身居中,而不是视口。这时候得用 align-self: anchor-center,它会以默认的锚点(也就是视口)为参照来垂直居中。两个属性组合起来,就能实现水平和垂直双方向居中。

嫌麻烦的话,可以直接用 place-self 这个简写属性。

.center-modal {
  position: absolute;
  place-self: anchor-center center;
}

place-self: anchor-center center 的第一个值是垂直方向的对齐方式(锚点居中),第二个值是水平方向的对齐方式(居中),一行代码就能把弹窗钉在屏幕正中央,比之前的偏移大法简洁了不止一个档次。

文本对比度与排版

自动高对比文字

给元素设置背景色的时候,选文字颜色总得纠结一下,选白色可能看不清,选黑色又可能刺眼。contrast-color() 函数就像一个智能小助手,它会自动判断在给定背景色上,是白色还是黑色的对比度更高,然后返回那个颜色。

.auto-contrast-card {
  --card-bg: coral;
  background-color: var(--card-bg);
  color: contrast-color(var(--card-bg));
}

这样一来,背景色变了,文字颜色也会自动跟着变成更容易看清的那个。不过得留个心眼,contrast-color() 目前只会返回纯黑或纯白,如果背景色本身很复杂,或者需要某种特定的主题色,这个函数可能就帮不上忙了。而且,它只考虑了颜色本身的对比度,没考虑字体粗细、大小这些因素,所以保险起见,最好还是用专业工具验证一下最终的对比度是否达标。

优雅文本折行

长篇大论的段落,英文排版里经常出现最后一行只有一个单词的“寡妇”现象,或者每一行长度参差不齐,看着特别不舒服。text-wrap: pretty 就是专门来解决这个痛点的。

p {
  text-wrap: pretty;
}

加了这一行样式之后,浏览器会自动优化文本的折行方式,避免出现孤零零的单词独占一行,同时尽量让每一行的长度保持相对均匀,还会控制连字符的使用频率。这样整个段落的视觉感会变得非常整齐,读起来也更顺滑。