[{"data":1,"prerenderedAt":995},["ShallowReactive",2],{"post-\u002Fposts\u002Fview-transitions":3,"post-nav-\u002Fposts\u002Fview-transitions":957,"post-related-\u002Fposts\u002Fview-transitions":976},{"id":4,"title":5,"body":6,"category":941,"cover":942,"date":943,"description":944,"draft":945,"extension":946,"featured":186,"meta":947,"navigation":186,"path":948,"pinned":945,"seo":949,"stem":950,"tags":951,"updated":955,"__hash__":956},"posts\u002Fposts\u002Fview-transitions.md","View Transitions 不是动画库",{"type":7,"value":8,"toc":930},"minimark",[9,18,21,25,33,36,58,61,64,118,121,126,129,134,141,329,332,336,347,627,637,641,648,676,679,754,757,765,771,776,779,787,876,879,882,907,914,917,923,926],[10,11,12,13,17],"p",{},"第一次看到 ",[14,15,16],"code",{},"document.startViewTransition"," 的时候，我以为它就是个更简单的 GSAP。",[10,19,20],{},"用了半年之后我发现，它真正解决的问题根本不是\"怎么动画\"。",[22,23,24],"h2",{"id":24},"它解决的是什么问题",[10,26,27,28,32],{},"传统的页面转场动画难写，不是因为 CSS 难，而是因为",[29,30,31],"strong",{},"你要同时管理两个状态","。",[10,34,35],{},"从列表页跳到详情页，你得：",[37,38,39,43,46,49,52,55],"ol",{},[40,41,42],"li",{},"给旧页面加一个 \"leaving\" 类",[40,44,45],{},"等动画结束",[40,47,48],{},"卸载旧页面",[40,50,51],{},"挂载新页面",[40,53,54],{},"给新页面加一个 \"entering\" 类",[40,56,57],{},"再等动画结束",[10,59,60],{},"六个步骤，任何一步时序错一点就会闪。",[10,62,63],{},"View Transitions 把这六步变成了一个 API：",[65,66,71],"pre",{"className":67,"code":68,"language":69,"meta":70,"style":70},"language-ts shiki shiki-themes github-light github-dark","document.startViewTransition(() => {\n  \u002F\u002F 只在这里改 DOM，浏览器负责前后的快照和过渡\n  renderNewPage()\n})\n","ts","",[14,72,73,96,103,112],{"__ignoreMap":70},[74,75,78,82,86,89,93],"span",{"class":76,"line":77},"line",1,[74,79,81],{"class":80},"sVt8B","document.",[74,83,85],{"class":84},"sScJk","startViewTransition",[74,87,88],{"class":80},"(() ",[74,90,92],{"class":91},"szBVR","=>",[74,94,95],{"class":80}," {\n",[74,97,99],{"class":76,"line":98},2,[74,100,102],{"class":101},"sJ8bj","  \u002F\u002F 只在这里改 DOM，浏览器负责前后的快照和过渡\n",[74,104,106,109],{"class":76,"line":105},3,[74,107,108],{"class":84},"  renderNewPage",[74,110,111],{"class":80},"()\n",[74,113,115],{"class":76,"line":114},4,[74,116,117],{"class":80},"})\n",[10,119,120],{},"浏览器会：对旧状态截图 → 执行你的回调 → 对新状态截图 → 在两帧之间做过渡。",[10,122,123],{},[29,124,125],{},"你只描述\"变成什么样\"，不描述\"怎么变\"。",[22,127,128],{"id":128},"三种实际用法",[130,131,133],"h3",{"id":132},"一页面切换","一、页面切换",[10,135,136,137,140],{},"Nuxt 开了 ",[14,138,139],{},"experimental.viewTransition"," 之后，页面过渡可以完全用 CSS 控制：",[65,142,146],{"className":143,"code":144,"language":145,"meta":70,"style":70},"language-css shiki shiki-themes github-light github-dark","::view-transition-old(root) {\n  animation: 200ms ease-out both fade-out;\n}\n\n::view-transition-new(root) {\n  animation: 200ms ease-out both fade-in;\n}\n\n@keyframes fade-out {\n  to { opacity: 0; }\n}\n\n@keyframes fade-in {\n  from {\n    opacity: 0;\n    transform: translateY(4px);\n  }\n}\n","css",[14,147,148,153,177,182,188,194,212,217,222,234,254,259,264,274,282,295,318,324],{"__ignoreMap":70},[74,149,150],{"class":76,"line":77},[74,151,152],{"class":80},"::view-transition-old(root) {\n",[74,154,155,159,162,165,168,171,174],{"class":76,"line":98},[74,156,158],{"class":157},"sj4cs","  animation",[74,160,161],{"class":80},": ",[74,163,164],{"class":157},"200",[74,166,167],{"class":91},"ms",[74,169,170],{"class":157}," ease-out",[74,172,173],{"class":157}," both",[74,175,176],{"class":80}," fade-out;\n",[74,178,179],{"class":76,"line":105},[74,180,181],{"class":80},"}\n",[74,183,184],{"class":76,"line":114},[74,185,187],{"emptyLinePlaceholder":186},true,"\n",[74,189,191],{"class":76,"line":190},5,[74,192,193],{"class":80},"::view-transition-new(root) {\n",[74,195,197,199,201,203,205,207,209],{"class":76,"line":196},6,[74,198,158],{"class":157},[74,200,161],{"class":80},[74,202,164],{"class":157},[74,204,167],{"class":91},[74,206,170],{"class":157},[74,208,173],{"class":157},[74,210,211],{"class":80}," fade-in;\n",[74,213,215],{"class":76,"line":214},7,[74,216,181],{"class":80},[74,218,220],{"class":76,"line":219},8,[74,221,187],{"emptyLinePlaceholder":186},[74,223,225,228,232],{"class":76,"line":224},9,[74,226,227],{"class":91},"@keyframes",[74,229,231],{"class":230},"s4XuR"," fade-out",[74,233,95],{"class":80},[74,235,237,240,243,246,248,251],{"class":76,"line":236},10,[74,238,239],{"class":84},"  to",[74,241,242],{"class":80}," { ",[74,244,245],{"class":157},"opacity",[74,247,161],{"class":80},[74,249,250],{"class":157},"0",[74,252,253],{"class":80},"; }\n",[74,255,257],{"class":76,"line":256},11,[74,258,181],{"class":80},[74,260,262],{"class":76,"line":261},12,[74,263,187],{"emptyLinePlaceholder":186},[74,265,267,269,272],{"class":76,"line":266},13,[74,268,227],{"class":91},[74,270,271],{"class":230}," fade-in",[74,273,95],{"class":80},[74,275,277,280],{"class":76,"line":276},14,[74,278,279],{"class":84},"  from",[74,281,95],{"class":80},[74,283,285,288,290,292],{"class":76,"line":284},15,[74,286,287],{"class":157},"    opacity",[74,289,161],{"class":80},[74,291,250],{"class":157},[74,293,294],{"class":80},";\n",[74,296,298,301,303,306,309,312,315],{"class":76,"line":297},16,[74,299,300],{"class":157},"    transform",[74,302,161],{"class":80},[74,304,305],{"class":157},"translateY",[74,307,308],{"class":80},"(",[74,310,311],{"class":157},"4",[74,313,314],{"class":91},"px",[74,316,317],{"class":80},");\n",[74,319,321],{"class":76,"line":320},17,[74,322,323],{"class":80},"  }\n",[74,325,327],{"class":76,"line":326},18,[74,328,181],{"class":80},[10,330,331],{},"注意这里没有 JS。转场逻辑全在 CSS 里，JS 只负责换内容。",[130,333,335],{"id":334},"二主题切换的圆形扩散","二、主题切换的圆形扩散",[10,337,338,339,342,343,346],{},"这个效果很多人以为是手写 ",[14,340,341],{},"clip-path"," 加 ",[14,344,345],{},"requestAnimationFrame","，其实可以完全交给 View Transitions：",[65,348,350],{"className":67,"code":349,"language":69,"meta":70,"style":70},"async function toggleTheme(x: number, y: number) {\n  const radius = Math.hypot(\n    Math.max(x, innerWidth - x),\n    Math.max(y, innerHeight - y),\n  )\n\n  const transition = document.startViewTransition(() => {\n    document.documentElement.classList.toggle('dark')\n  })\n\n  await transition.ready\n\n  document.documentElement.animate(\n    {\n      clipPath: [\n        `circle(0px at ${x}px ${y}px)`,\n        `circle(${radius}px at ${x}px ${y}px)`,\n      ],\n    },\n    {\n      duration: 200,\n      easing: 'ease-out',\n      pseudoElement: '::view-transition-new(root)',\n    },\n  )\n}\n",[14,351,352,387,407,424,438,443,447,467,484,489,493,501,505,515,520,525,543,564,569,575,580,590,601,612,617,622],{"__ignoreMap":70},[74,353,354,357,360,363,365,368,371,374,377,380,382,384],{"class":76,"line":77},[74,355,356],{"class":91},"async",[74,358,359],{"class":91}," function",[74,361,362],{"class":84}," toggleTheme",[74,364,308],{"class":80},[74,366,367],{"class":230},"x",[74,369,370],{"class":91},":",[74,372,373],{"class":157}," number",[74,375,376],{"class":80},", ",[74,378,379],{"class":230},"y",[74,381,370],{"class":91},[74,383,373],{"class":157},[74,385,386],{"class":80},") {\n",[74,388,389,392,395,398,401,404],{"class":76,"line":98},[74,390,391],{"class":91},"  const",[74,393,394],{"class":157}," radius",[74,396,397],{"class":91}," =",[74,399,400],{"class":80}," Math.",[74,402,403],{"class":84},"hypot",[74,405,406],{"class":80},"(\n",[74,408,409,412,415,418,421],{"class":76,"line":105},[74,410,411],{"class":80},"    Math.",[74,413,414],{"class":84},"max",[74,416,417],{"class":80},"(x, innerWidth ",[74,419,420],{"class":91},"-",[74,422,423],{"class":80}," x),\n",[74,425,426,428,430,433,435],{"class":76,"line":114},[74,427,411],{"class":80},[74,429,414],{"class":84},[74,431,432],{"class":80},"(y, innerHeight ",[74,434,420],{"class":91},[74,436,437],{"class":80}," y),\n",[74,439,440],{"class":76,"line":190},[74,441,442],{"class":80},"  )\n",[74,444,445],{"class":76,"line":196},[74,446,187],{"emptyLinePlaceholder":186},[74,448,449,451,454,456,459,461,463,465],{"class":76,"line":214},[74,450,391],{"class":91},[74,452,453],{"class":157}," transition",[74,455,397],{"class":91},[74,457,458],{"class":80}," document.",[74,460,85],{"class":84},[74,462,88],{"class":80},[74,464,92],{"class":91},[74,466,95],{"class":80},[74,468,469,472,475,477,481],{"class":76,"line":219},[74,470,471],{"class":80},"    document.documentElement.classList.",[74,473,474],{"class":84},"toggle",[74,476,308],{"class":80},[74,478,480],{"class":479},"sZZnC","'dark'",[74,482,483],{"class":80},")\n",[74,485,486],{"class":76,"line":224},[74,487,488],{"class":80},"  })\n",[74,490,491],{"class":76,"line":236},[74,492,187],{"emptyLinePlaceholder":186},[74,494,495,498],{"class":76,"line":256},[74,496,497],{"class":91},"  await",[74,499,500],{"class":80}," transition.ready\n",[74,502,503],{"class":76,"line":261},[74,504,187],{"emptyLinePlaceholder":186},[74,506,507,510,513],{"class":76,"line":266},[74,508,509],{"class":80},"  document.documentElement.",[74,511,512],{"class":84},"animate",[74,514,406],{"class":80},[74,516,517],{"class":76,"line":276},[74,518,519],{"class":80},"    {\n",[74,521,522],{"class":76,"line":284},[74,523,524],{"class":80},"      clipPath: [\n",[74,526,527,530,532,535,537,540],{"class":76,"line":297},[74,528,529],{"class":479},"        `circle(0px at ${",[74,531,367],{"class":80},[74,533,534],{"class":479},"}px ${",[74,536,379],{"class":80},[74,538,539],{"class":479},"}px)`",[74,541,542],{"class":80},",\n",[74,544,545,548,551,554,556,558,560,562],{"class":76,"line":320},[74,546,547],{"class":479},"        `circle(${",[74,549,550],{"class":80},"radius",[74,552,553],{"class":479},"}px at ${",[74,555,367],{"class":80},[74,557,534],{"class":479},[74,559,379],{"class":80},[74,561,539],{"class":479},[74,563,542],{"class":80},[74,565,566],{"class":76,"line":326},[74,567,568],{"class":80},"      ],\n",[74,570,572],{"class":76,"line":571},19,[74,573,574],{"class":80},"    },\n",[74,576,578],{"class":76,"line":577},20,[74,579,519],{"class":80},[74,581,583,586,588],{"class":76,"line":582},21,[74,584,585],{"class":80},"      duration: ",[74,587,164],{"class":157},[74,589,542],{"class":80},[74,591,593,596,599],{"class":76,"line":592},22,[74,594,595],{"class":80},"      easing: ",[74,597,598],{"class":479},"'ease-out'",[74,600,542],{"class":80},[74,602,604,607,610],{"class":76,"line":603},23,[74,605,606],{"class":80},"      pseudoElement: ",[74,608,609],{"class":479},"'::view-transition-new(root)'",[74,611,542],{"class":80},[74,613,615],{"class":76,"line":614},24,[74,616,574],{"class":80},[74,618,620],{"class":76,"line":619},25,[74,621,442],{"class":80},[74,623,625],{"class":76,"line":624},26,[74,626,181],{"class":80},[10,628,629,630,633,634,32],{},"关键在 ",[14,631,632],{},"pseudoElement: '::view-transition-new(root)'","——你不是在动画真实 DOM，是在动画浏览器的过渡快照层。所以这个动画",[29,635,636],{},"不会触发重排",[130,638,640],{"id":639},"三列表到详情的共享元素","三、列表到详情的共享元素",[10,642,643,644,647],{},"给元素一个 ",[14,645,646],{},"view-transition-name","：",[65,649,651],{"className":143,"code":650,"language":145,"meta":70,"style":70},".post-card__title { view-transition-name: post-title; }\n.post-detail__title { view-transition-name: post-title; }\n",[14,652,653,665],{"__ignoreMap":70},[74,654,655,658,660,662],{"class":76,"line":77},[74,656,657],{"class":84},".post-card__title",[74,659,242],{"class":80},[74,661,646],{"class":157},[74,663,664],{"class":80},": post-title; }\n",[74,666,667,670,672,674],{"class":76,"line":98},[74,668,669],{"class":84},".post-detail__title",[74,671,242],{"class":80},[74,673,646],{"class":157},[74,675,664],{"class":80},[10,677,678],{},"两个页面上同名，浏览器就会把它们当成同一个元素，自动做位置和尺寸的插值。",[65,680,682],{"className":143,"code":681,"language":145,"meta":70,"style":70},"::view-transition-old(post-title),\n::view-transition-new(post-title) {\n  animation-duration: 250ms;\n  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n}\n",[14,683,684,696,705,719,750],{"__ignoreMap":70},[74,685,686,689,693],{"class":76,"line":77},[74,687,688],{"class":80},"::view-transition-old(",[74,690,692],{"class":691},"s9eBZ","post-title",[74,694,695],{"class":80},"),\n",[74,697,698,701,703],{"class":76,"line":98},[74,699,700],{"class":80},"::view-transition-new(",[74,702,692],{"class":691},[74,704,386],{"class":80},[74,706,707,710,712,715,717],{"class":76,"line":105},[74,708,709],{"class":157},"  animation-duration",[74,711,161],{"class":80},[74,713,714],{"class":157},"250",[74,716,167],{"class":91},[74,718,294],{"class":80},[74,720,721,724,726,729,731,734,736,738,740,743,745,748],{"class":76,"line":114},[74,722,723],{"class":157},"  animation-timing-function",[74,725,161],{"class":80},[74,727,728],{"class":157},"cubic-bezier",[74,730,308],{"class":80},[74,732,733],{"class":157},"0.4",[74,735,376],{"class":80},[74,737,250],{"class":157},[74,739,376],{"class":80},[74,741,742],{"class":157},"0.2",[74,744,376],{"class":80},[74,746,747],{"class":157},"1",[74,749,317],{"class":80},[74,751,752],{"class":76,"line":190},[74,753,181],{"class":80},[22,755,756],{"id":756},"必须知道的三个限制",[10,758,759],{},[29,760,761,762,764],{},"一、同一时间只能有一个 ",[14,763,646],{}," 在页面上。",[10,766,767,768,770],{},"如果列表里有 10 张卡片都叫 ",[14,769,692],{},"，浏览器会直接报错、整个过渡失效。所以列表页通常不给每张卡单独命名，只在用户点击时动态设置。",[10,772,773],{},[29,774,775],{},"二、动画期间页面不可交互。",[10,777,778],{},"过渡快照覆盖在真实 DOM 之上。所以别做超过 300ms 的转场。",[10,780,781],{},[29,782,783,784,32],{},"三、必须尊重 ",[14,785,786],{},"prefers-reduced-motion",[65,788,790],{"className":67,"code":789,"language":69,"meta":70,"style":70},"const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches\nconst supported = 'startViewTransition' in document\n\nif (!supported || reduceMotion) {\n  \u002F\u002F 直接切，不做动画\n  applyChange()\n  return\n}\n",[14,791,792,813,831,835,855,860,867,872],{"__ignoreMap":70},[74,793,794,797,800,802,805,807,810],{"class":76,"line":77},[74,795,796],{"class":91},"const",[74,798,799],{"class":157}," reduceMotion",[74,801,397],{"class":91},[74,803,804],{"class":84}," matchMedia",[74,806,308],{"class":80},[74,808,809],{"class":479},"'(prefers-reduced-motion: reduce)'",[74,811,812],{"class":80},").matches\n",[74,814,815,817,820,822,825,828],{"class":76,"line":98},[74,816,796],{"class":91},[74,818,819],{"class":157}," supported",[74,821,397],{"class":91},[74,823,824],{"class":479}," 'startViewTransition'",[74,826,827],{"class":91}," in",[74,829,830],{"class":80}," document\n",[74,832,833],{"class":76,"line":105},[74,834,187],{"emptyLinePlaceholder":186},[74,836,837,840,843,846,849,852],{"class":76,"line":114},[74,838,839],{"class":91},"if",[74,841,842],{"class":80}," (",[74,844,845],{"class":91},"!",[74,847,848],{"class":80},"supported ",[74,850,851],{"class":91},"||",[74,853,854],{"class":80}," reduceMotion) {\n",[74,856,857],{"class":76,"line":190},[74,858,859],{"class":101},"  \u002F\u002F 直接切，不做动画\n",[74,861,862,865],{"class":76,"line":196},[74,863,864],{"class":84},"  applyChange",[74,866,111],{"class":80},[74,868,869],{"class":76,"line":214},[74,870,871],{"class":91},"  return\n",[74,873,874],{"class":76,"line":219},[74,875,181],{"class":80},[10,877,878],{},"这不是可选项。前庭功能障碍的用户会因为这种动画头晕。",[22,880,881],{"id":881},"什么时候不该用",[883,884,885,891,901],"ul",{},[40,886,887,890],{},[29,888,889],{},"表单提交后的状态更新"," —— 不需要转场，用户只想知道成功了没",[40,892,893,896,897,900],{},[29,894,895],{},"列表项的 hover 效果"," —— 那是微交互，用 ",[14,898,899],{},"transition"," 就够了",[40,902,903,906],{},[29,904,905],{},"超过 300ms 的复杂编排"," —— 会让人等",[10,908,909,910,913],{},"View Transitions 的定位是",[29,911,912],{},"页面级的状态切换","，不是\"万能动画方案\"。",[22,915,916],{"id":916},"一句话",[918,919,920],"blockquote",{},[10,921,922],{},"它把\"两套状态之间怎么过渡\"这件麻烦事，从你手里拿走了。",[10,924,925],{},"这可能不是最酷的动画 API，但它是我用过最省心的那个。",[927,928,929],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#57606A;--shiki-dark:#9AA4B0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":70,"searchDepth":98,"depth":98,"links":931},[932,933,938,939,940],{"id":24,"depth":98,"text":24},{"id":128,"depth":98,"text":128,"children":934},[935,936,937],{"id":132,"depth":105,"text":133},{"id":334,"depth":105,"text":335},{"id":639,"depth":105,"text":640},{"id":756,"depth":98,"text":756},{"id":881,"depth":98,"text":881},{"id":916,"depth":98,"text":916},"前端","\u002Fimages\u002Fcovers\u002Fview-transition.png","2025-09-18","用它做页面切换、做主题扩散、做列表到详情的共享元素过渡。它真正的价值不是\"让页面动起来\"，而是让你不必手动管理进入和离开两套状态。",false,"md",{},"\u002Fposts\u002Fview-transitions",{"title":5,"description":944},"posts\u002Fview-transitions",[952,953,954],"动画","浏览器","CSS",null,"o7V2tHjxITWwXXurDYWbCTqgoyRUTuBswN4Sl1jHiL4",{"prev":958,"next":967},{"path":959,"title":960,"description":961,"date":962,"updated":955,"tags":963,"category":965,"cover":966,"draft":945,"pinned":186,"featured":945},"\u002Fposts\u002Ftypography-over-color","排版比配色更决定气质","把同样一套颜色交给两个人排，出来的东西可以完全不同。这篇讲我怎么用字重、行距和留白，在不换颜色的前提下改变一个页面的脾气。","2025-10-28",[964,965,954],"排版","设计","\u002Fimages\u002Fcovers\u002Ftypography.png",{"path":968,"title":969,"description":970,"date":971,"updated":955,"tags":972,"category":941,"cover":955,"draft":945,"pinned":945,"featured":945},"\u002Fposts\u002Fself-hosted-fonts","自托管字体的一年","从 Google Fonts 换成自托管，从自托管换成子集化分片。聊聊为什么一个中文字体能从 25MB 变成实际加载 180KB。","2025-08-08",[973,974,975],"字体","性能","中文",[977,985,987],{"path":978,"title":979,"description":980,"date":981,"updated":955,"tags":982,"category":941,"cover":955,"draft":945,"pinned":945,"featured":945},"\u002Fposts\u002Fcss-variables-as-api","我把 CSS 变量当成了 API","设计 token 写进配置文件，构建期编译成 CSS 变量，组件只读变量。这套做法让「改一处，全站生效」从口号变成了构建期保证。","2025-04-20",[954,983,984],"设计系统","构建",{"path":968,"title":969,"description":970,"date":971,"updated":955,"tags":986,"category":941,"cover":955,"draft":945,"pinned":945,"featured":945},[973,974,975],{"path":988,"title":989,"description":990,"date":991,"updated":955,"tags":992,"category":941,"cover":955,"draft":945,"pinned":945,"featured":945},"\u002Fposts\u002Fno-loading-feel","无加载不是速度，是连续感","一个 300ms 的白屏比一个 800ms 的骨架屏更让人焦虑。这篇讲怎么用 SSG、预取和 view transition 把页面跳转做成「一瞬间的事」。","2025-05-15",[974,993,994],"体验","Nuxt",1794113901485]