登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

View Transitions 怎样保留页面过渡状态

来源:17golang原创

时间:2026-10-25 15:05:13 100浏览 收藏

热门推荐
漫画APP
动画内容聚合,热门资源快捷查看
立即下载

要在 View Transitions 期间保留草稿、选中项等页面状态,应把业务状态放在独立模型里,并在 document.startViewTransition() 的更新回调中提交 DOM 变化。View Transitions 保留的是用于视觉衔接的旧、新视图快照,不会自动替应用持久化表单、路由或滚动状态。

本文限定同一文档内的视图切换,用草稿是否保留、每次提交是否只发生一次来观察行为。示例为原创,未在浏览器执行;没有声称测得帧率、耗时或性能提升。

API 参考:https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition。生命周期参考:https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition。规范入口:https://www.w3.org/TR/css-view-transitions-1/。

先定义观察项:草稿、视图和更新次数

先确定什么叫“状态还在”。本文的 state.draft 是草稿备注,state.page 是概览或详情,commitCount 是已提交的视图切换次数。过渡是否播放属于表现层;即使动画被跳过,业务内容也应更新,草稿也应继续属于同一份模型。

如果页面切换时直接销毁所有组件,只在输入框 DOM 中存值,状态丢失的根因是应用没有保存它。给元素增加 view-transition-name 可以帮助视觉对应,但不会把旧输入框自动变成新输入框的数据来源。

最小思路:业务状态独立于过渡快照

最小结构是三部分:state 负责数据,render 负责把数据写到内容容器,ViewTransition 对象负责这次视觉过渡的完成信号。输入框放在稳定区域,只替换内容容器,避免本例把焦点恢复和表单重建问题一起引入。

同一项 commit 应同时服务动画路径和降级路径。不要在动画完成后再次渲染一次,也不要在 ready 被拒绝时重复调用带副作用的提交函数。视觉跳过和业务更新失败需要分别解释。

图1:业务模型、渲染函数和旧新快照的职责关系
图1:业务模型、渲染函数和旧新快照的职责关系。静态结构说明图,不是浏览器截图或动画时序。

改动点:在更新回调里提交新视图

先准备稳定的输入区、切换按钮和内容区:




样式只给内容区一个独立名称。本例依靠浏览器默认过渡效果,不增加自定义动画:

/* 本例只给一个稳定容器命名,避免同屏名称重复。 */
#view { view-transition-name: content-panel; }

把脚本放在上述元素之后,或作为延迟执行的脚本加载:

const draft = document.querySelector("#draft");
const toggle = document.querySelector("#toggle");
const view = document.querySelector("#view");
const status = document.querySelector("#status");
// 业务状态有自己的对象,不从快照或过渡伪元素里读取。
const state = { page: "概览", draft: draft.value };
let busy = false;
let commitCount = 0;

function render() {
  const paragraph = document.createElement("p");
  paragraph.textContent = `${state.page}:${state.draft}`;
  view.replaceChildren(paragraph);
}

draft.addEventListener("input", () => {
  state.draft = draft.value;
  render(); // 普通输入不必强行附带页面过渡。
});

toggle.addEventListener("click", async () => {
  if (busy) return; // 本例采用忽略进行中重复请求的策略。
  busy = true;
  toggle.disabled = true;
  draft.disabled = true;
  const nextPage = state.page === "概览" ? "详情" : "概览";
  const commit = () => {
    state.page = nextPage;
    render();
    commitCount += 1;
  };
  try {
    const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (typeof document.startViewTransition !== "function" || reduceMotion) {
      commit(); // 降级仍提交同一份业务更新。
    } else {
      const transition = document.startViewTransition(commit);
      // 动画准备失败不代表需要再次执行 commit。
      transition.ready.catch(() => {});
      // 立即订阅两项结果,更新失败时在外层显式报告。
      await Promise.all([
        transition.updateCallbackDone,
        transition.finished,
      ]);
    }
    status.textContent = `视图已更新,提交次数:${commitCount}`;
  } catch (error) {
    console.error("视图更新失败", error);
    status.textContent = "更新失败,请检查业务渲染;不要自动重复提交。";
  } finally {
    busy = false;
    toggle.disabled = false;
    draft.disabled = false;
  }
});
render();

busy 是本例选择的并发请求策略:进行中不再接受另一次切换,同时暂时禁用输入,避免用户在等待旧快照期间继续改动本例草稿。它不是所有产品都必须采用的策略;需要排队或最后一次请求优先时,应另行定义。

代码没有重建输入框,state.draft 也不因切换而清空。业务复杂时,可把 state 换成框架状态或应用存储,但仍需保证 DOM 提交发生在所用框架真正完成渲染的边界里,而非仅仅调用异步更新函数就当作完成。

图2:ViewTransition 对象与三项完成信号、输入锁的静态关系
图2:ViewTransition 对象与三项完成信号、输入锁的静态关系。静态结构说明图,不是浏览器截图或动画时序。

复现方法:比较动画与直接更新分支

将标记、样式和脚本组合到自己的示例页后,可以按下面几种场景观察。它们是供读者复现的检查点,不是本文已经执行的测试报告。

场景应观察的业务行为
输入备注后切换概览和详情草稿文本保留,只改变 page
一次切换完成commitCount 增加一次
切换过程中再次点击本例忽略重复请求
API 不可用直接提交同一更新
用户偏好减少动态效果跳过动画但保留更新能力

故意让业务 render 抛错时,应看到失败提示而不是重复提交。真实项目还应定义业务状态的回滚或恢复方式;View Transitions 不会替应用撤销已经执行的 DOM 或数据副作用。

结果对比:完成信号各自代表什么

信号适合表达什么不要误认为
updateCallbackDone更新回调已完成过渡动画已播完
ready过渡伪元素已准备好,可开始动画表单已保存到服务器
finished过渡完成,或跳过后达到结束状态必须重新提交业务更新

本例同时等待更新与过渡结束,随后在 finally 释放 busy 和按钮。ready 的拒绝被单独订阅,不触发提交重试;更新错误仍由 updateCallbackDone 和外层处理报告。业务若需要把失败状态反馈给路由层,还应把这一结果纳入自己的调用约定。

这个改动改善的是职责清晰度和更新次数的可解释性。没有负载数据时,不能把增加 View Transitions 描述为速度优化;视觉连续性与实际响应耗时是两项不同的产品指标。

边界条件与常见问题

刷新页面后草稿会自动恢复吗?

不会。本例只有内存中的 state;跨刷新需要应用自行选择持久化或服务端恢复方案。快照不能充当业务数据库。

skipTransition 会取消 DOM 更新吗?

它跳过动画部分,不跳过用于更新 DOM 的回调。不要把它当作网络请求取消或数据回滚按钮。

为什么同名元素可能导致过渡不能播放?

命名元素需要满足过渡快照的匹配约束,本例只给一个稳定内容容器命名。列表里为多个同时可见元素写相同名称,不能作为“保留所有节点”的办法。

跨文档导航能直接照抄吗?

本文只使用 startViewTransition 的同文档路径。跨文档导航有不同的启用条件和生命周期;路由状态、草稿以及返回时的滚动位置仍需应用定义,不应把此示例当完整跨页恢复方案。

浏览器一定支持全部扩展能力吗?

本例先检测使用的方法,并始终提供直接更新分支。不要因为看到接口存在,就推断所有扩展参数、跨页能力和目标设备组合完全一致。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>