玩转CSS新特性,那些让布局起飞的神操作到底怎么用?

3,560字
15–23 分钟
in

最近前端圈子里聊得最嗨的就是CSS一堆新玩意儿,从:has()到锚点定位,从滚动时间线到自动高度过渡,简直像开了挂。以前写个手风琴菜单还得靠JS偷摸算高度,现在几行CSS直接搞定,再也不用担心动画翻车。这篇文章就掰扯掰扯这些新特性到底咋用,顺便带大伙过一遍实际干活时碰到的坑和骚操作。

目录

啥是CSS父选择器

:has()这玩意儿就像个透视眼,能根据后代元素来选中父级。以前想给包含图片的卡片加个特殊边框?得JS遍历。现在:has()一出马,直接.card:has(img)就搞定。这货兼容性已经稳了,各大浏览器全支持,放心冲。

自动高度过渡咋整

过渡到height: auto曾经是CSS的噩梦,现在用grid配合grid-template-rows就能丝滑搞定。下面这套流程专治各种折叠面板翻车。

折叠面板平滑开合

第一步:搭骨架
写一个HTML结构,外层是触发按钮,内层是内容区。按钮负责切换一个open类,内容区包着真正的文本或图片。

<div class="collapse">
  <button class="collapse__btn">戳我展开</button>
  <div class="collapse__content">
    <p>这里随便塞点东西,文字、图片、视频都行,高度会自己变。</p>
  </div>
</div>

第二步:CSS关键三板斧
内容区先设置display: grid,然后grid-template-rows0fr过渡到1fr。注意要搭配overflow: hidden防止内容溢出。

.collapse__content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease;
  overflow: hidden;
}

.collapse.open .collapse__content {
  grid-template-rows: 1fr;
}

/* 内层必须限制最小高度0,否则1fr不生效 */
.collapse__content > * {
  min-height: 0;
}

第三步:JS只负责切类
别在JS里算高度,直接给父级.collapse toggle一下.open类。

document.querySelector('.collapse__btn').addEventListener('click', (e) => {
  e.currentTarget.closest('.collapse').classList.toggle('open');
});

敲黑板:内层那个min-height: 0是灵魂,少了它1fr会变成自动撑开,过渡直接消失。另外grid-template-rows过渡只支持fr和百分比,别想着用auto。这套方案在移动端丝滑得一匹,比max-height猜大小靠谱多了。

锚点定位粘性菜单

锚点定位能搞出一个固定在某个元素旁边的浮层,滚动时还跟屁虫一样跟着。写一个评论按钮旁边弹出工具条的案例,全程不用positiontop/left苦算偏移。

跟屁虫工具条

第一步:锚点关联
按钮用anchor-name起个名,工具条用position-anchor绑定这个名。

<button class="reply-btn" style="anchor-name: --reply">留言</button>
<div class="toolbar" style="position-anchor: --reply">👍 ❤️ 😂</div>

第二步:定位属性
工具条设置position: absolutefixed,然后用anchor()函数指定位置。

.toolbar {
  position: fixed;
  bottom: anchor(--reply top);
  left: anchor(--reply right);
  margin-left: 8px;
  background: white;
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
  border-radius: 20px;
  padding: 4px 12px;
}

第三步:滚动时自动跟随
因为用了position: fixed,页面滚动时锚点元素移动,工具条会自动重新计算位置。如果要用在滚动容器内,改成position: absolute配合相对定位的父级。

这里容易踩坑:锚点名称必须以--开头,像变量那样。anchor()函数第一个参数是锚点名,第二个是方向(top/right/bottom/left)。如果工具条被裁切了,可以加anchor-scroll属性调整行为。实测这个特性在Chromium内核已经稳了,Safari也在路上了。

容器查询响应式卡片

容器查询让组件可以根据父容器宽度变样式,而不是看视口。写一个卡片列表,卡片在窄容器里变竖排,在宽容器里变横排,不用媒体查询。

智能卡片布局

第一步:容器声明
给每个卡片的父级加上container-type: inline-size,名字随便起。

<div class="card-list">
  <div class="card-container" style="container-type: inline-size; container-name: cardbox">
    <div class="card">
      <img src="avatar.png">
      <div class="card__info">...</div>
    </div>
  </div>
  <!-- 重复多个卡片 -->
</div>

第二步:容器查询规则
card-container宽度大于300px时,卡片变成横向排列。

@container cardbox (min-width: 300px) {
  .card {
    display: flex;
    flex-direction: row;
    align-items: center;
  }
  .card img {
    width: 80px;
    height: 80px;
    margin-right: 16px;
  }
}

第三步:降级处理
没支持的浏览器会直接忽略@container,默认样式得写在外面。

.card {
  display: flex;
  flex-direction: column;
}

实际开发时,container-name别起太长的名,容易写错。而且容器查询不认display: contents的父级,那种父级会被忽略。这个特性现在三大浏览器都支持了,放心用。

多方案解决弹窗自动定位

有时候想搞一个点击按钮后,在按钮旁边弹出菜单,还要跟着滚动走。对比两套方案:锚点定位 vs 老派JS计算偏移。

方案优点缺点适用场景
锚点定位代码少 滚动自动跟兼容性稍窄现代项目 无IE
JS计算兼容性好 全平台代码多 滚动要监听老旧系统

锚点定位代码量:上面已经演示过,十几行CSS搞定。
JS计算方案:需要getBoundingClientRect,加滚动事件,还要防抖,一不留神就掉帧。

// 老派做法(演示用,实际项目请用锚点)
function updateToolbarPos() {
  const btn = document.querySelector('.reply-btn');
  const rect = btn.getBoundingClientRect();
  toolbar.style.top = rect.bottom + 'px';
  toolbar.style.left = rect.left + 'px';
}
window.addEventListener('scroll', () => requestAnimationFrame(updateToolbarPos));
window.addEventListener('resize', updateToolbarPos);

两相对比,新特性简直降维打击。除非要兼容古董浏览器,否则直接上锚点定位,省心又流畅。

滚动驱动动画

滚动时间线能让动画跟着滚动进度走,比如一个进度条随着页面滚动变宽。这玩意儿以前得写scroll事件算百分比,现在CSS一肩扛。

核心代码

.progress {
  height: 4px;
  background: blue;
  transform-origin: left;
  animation: fill 1s linear forwards;
  animation-timeline: scroll();
}

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

注意animation-timeline目前还在实验阶段,Chromium可以开flag尝鲜。实际项目可以用scroll-timeline配合view-timeline做更精细的控制。不过这货变化快,建议先用Intersection Observer做降级。