CSS View Transition过渡异常:切换标签时出现额外高度问题
问题描述
我正在用CSS View Transition开发标签页,将激活态的下划线动画改成了背景色过渡,但切换不同宽度的标签时,过渡过程中会出现额外高度,宽度相同的标签切换则无此问题。试过移除LI的height属性,改用top/bottom/left/right定位,问题仍未解决。请问要修改哪些样式或View Transition相关代码,才能修复这个过渡时的高度异常?

相关代码(React + styled-components)
import { useState, flushSync } from 'react'; import styled from 'styled-components'; // 假设标签数据定义如下 const items = [ { ID: 1, value: '首页' }, { ID: 2, value: '很长很长的标签名称' }, { ID: 3, value: '设置' }, ]; function App() { const [activeID, setActiveID] = useState(1); const handleClick = (ID) => { if (!document.startViewTransition) { setActiveID(ID); return; } document.startViewTransition(() => { flushSync(() => { setActiveID(ID); }); }); }; return ( <> <UL> {items.map((item) => ( <LI key={item.ID} $active={activeID === item.ID} onClick={() => handleClick(item.ID)} > {item.value} </LI> ))} </UL> </> ); } const UL = styled.ul` display: flex; list-style: none; position: relative; `; const LI = styled.li` padding: 0.5rem 1rem; position: relative; &:before { content: ""; display: block; position: absolute; width: 100%; height: 100%; top: 0; left: 0; background-color: ${({ $active }) => $active ? "hsl(0 0% 100% / .1)" : "transparent"}; border: ${({ $active }) => $active ? "1px solid hsl(0 0% 100% / .1)" : "1px solid transparent"}; border-radius: 0.5rem; view-transition-name: ${({ $active }) => ($active ? "highlight" : "")}; } `; export default App;
解决方案
这个问题的核心是:不同宽度的标签项(LI)因内容长度差异,实际高度存在细微区别,View Transition在插值过渡时会放大这种差异,导致出现额外高度。可以通过两种方式修复:
方式一:统一标签项高度(简单快速)
强制所有标签项高度一致,消除高度差异带来的过渡异常:
- 修改LI的样式,添加固定高度并确保文字垂直居中:
const LI = styled.li` padding: 0.5rem 1rem; position: relative; height: 2.5rem; /* 根据你的设计调整合适高度 */ display: flex; align-items: center; /* 保证文字垂直居中 */ `;
- 全局添加View Transition样式约束,避免过渡时的尺寸拉伸:
/* 在全局样式文件或style标签中添加 */ ::view-transition-new(highlight), ::view-transition-old(highlight) { height: 100%; transform-origin: center; }
方式二:分离高亮元素(更稳定)
把高亮背景从伪元素改成独立的绝对定位元素,脱离标签项的尺寸依赖,彻底避免高度问题:
- 修改App组件,添加独立的高亮元素:
function App() { const [activeID, setActiveID] = useState(1); const activeRef = useRef(null); const handleClick = (ID) => { if (!document.startViewTransition) { setActiveID(ID); return; } document.startViewTransition(() => { flushSync(() => { setActiveID(ID); }); }); }; return ( <> <UL> {/* 独立的高亮背景元素 */} <Highlight ref={activeRef} /> {items.map((item) => ( <LI key={item.ID} $isActive={activeID === item.ID} onClick={() => handleClick(item.ID)} ref={el => activeID === item.ID && (activeRef.current = el)} > {item.value} </LI> ))} </UL> </> ); }
- 重新定义高亮和容器样式:
const UL = styled.ul` display: flex; list-style: none; position: relative; align-items: center; `; const LI = styled.li` padding: 0.5rem 1rem; position: relative; `; const Highlight = styled.div` position: absolute; top: 0; bottom: 0; left: ${({ current }) => current?.offsetLeft || 0}px; width: ${({ current }) => current?.offsetWidth || 0}px; background-color: hsl(0 0% 100% / .1); border: 1px solid hsl(0 0% 100% / .1); border-radius: 0.5rem; view-transition-name: highlight; `;
这种方式让高亮元素直接复用激活标签的尺寸,过渡时只处理位置和宽度变化,完全规避高度差异的影响。
内容的提问来源于stack exchange,提问作者Clayton Ingalls
相关产品推荐
相关产品推荐

