如何为可伸缩的span元素实现单View Transition过渡效果?
解决View Transition API过渡仅作用于目标文本的问题
当前你的过渡效果会覆盖整个页面,核心原因是:
- 全局的
::view-transition-old(*)/::view-transition-new(*)选择器会匹配所有过渡元素,没有限定范围; - 目标文本容器没有设置合适的布局属性,导致过渡伪元素默认继承全屏尺寸。
以下是修正后的实现方案,确保过渡仅作用于目标文本:
修正后的CSS
/* 让目标文本容器以inline-block布局,过渡基于文本实际尺寸 */ .terminal-inverted { display: inline-block; position: relative; overflow: hidden; background: #000; color: #fff; padding: 2px 4px; } /* 仅匹配我们自定义的view-transition-name前缀(t_)的过渡元素 */ ::view-transition-old([view-transition-name^="t_"]), ::view-transition-new([view-transition-name^="t_"]) { height: auto; width: auto; max-width: 100%; position: absolute; top: 0; left: 0; background: inherit; color: inherit; } @keyframes transition-out { from { max-width: 100%; opacity: 1; white-space: nowrap; } to { max-width: 0; opacity: 0; white-space: nowrap; overflow: hidden; } } @keyframes transition-in { from { max-width: 0; opacity: 0; white-space: nowrap; overflow: hidden; } to { max-width: 100%; opacity: 1; white-space: nowrap; } } /* 为旧元素和新元素分别绑定动画 */ ::view-transition-old([view-transition-name^="t_"]) { animation: 0.5s transition-out ease forwards; } ::view-transition-new([view-transition-name^="t_"]) { animation: 0.5s transition-in ease forwards; }
简化版核心JS(直接操作DOM)
let id = 0; const container = document.getElementById('text-container'); async function animate(messages, time) { const messagesCopy = [...messages]; const empty = createTextElement(' '); const first = createTextElement(messagesCopy.shift()); if (document.startViewTransition) { container.appendChild(empty); let currentElement = empty; // 首次过渡:空文本 → 第一个内容 const transitionInit = document.startViewTransition(() => { container.replaceChild(first, currentElement); currentElement = first; }); await transitionInit.finished; await delay(time); // 循环切换后续内容 for (const message of messagesCopy) { // 先过渡到空 const emptyElement = createTextElement(' '); const transitionOut = document.startViewTransition(() => { container.replaceChild(emptyElement, currentElement); currentElement = emptyElement; }); await transitionOut.finished; // 再过渡到目标文本 const targetElement = createTextElement(message); const transitionIn = document.startViewTransition(() => { container.replaceChild(targetElement, currentElement); currentElement = targetElement; }); await transitionIn.finished; await delay(time); } } else { // 降级处理:无过渡直接切换 let currentElement = createTextElement(messagesCopy.shift()); container.appendChild(currentElement); await delay(time); for (const message of messagesCopy) { const targetElement = createTextElement(message); container.replaceChild(targetElement, currentElement); currentElement = targetElement; await delay(time); } } id++; } // 创建带唯一view-transition-name的文本元素 function createTextElement(text) { const span = document.createElement('span'); span.className = 'terminal-inverted'; span.style.viewTransitionName = `t_${id}`; span.textContent = text; return span; } // 通用延迟函数 function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); } // 启动演示 animate(['Hello', 'View Transition', 'API', 'Custom Demo'], 1500);
关键调整说明
- 布局约束:给文本容器设置
display: inline-block,让过渡伪元素的尺寸贴合文本实际宽度,而非全屏。 - 精准选择:使用
[view-transition-name^="t_"]属性选择器,仅对我们自定义命名的过渡元素生效,避免影响全局。 - 动画优化:添加
white-space: nowrap和overflow: hidden,防止文本在过渡过程中换行,保证动画的流畅性。 - 定位控制:将过渡伪元素设为
absolute,相对于原文本容器定位,确保过渡效果完全覆盖目标文本区域。
内容的提问来源于stack exchange,提问作者jcubic
相关产品推荐
相关产品推荐

