自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

太絲滑了!了解一下原生的視圖轉(zhuǎn)換動(dòng)畫(huà) View Transitions API

開(kāi)發(fā) 前端
Android 里一般稱之為共享元素(shareElement)動(dòng)畫(huà),也就是動(dòng)畫(huà)前后有一個(gè)(或多個(gè))相同的元素,起到前后過(guò)渡的效果,可以很清楚的看到元素的變化過(guò)程,而并不是簡(jiǎn)單的消失和出現(xiàn)。

在原生 APP 中,我們經(jīng)常會(huì)看到那種絲滑又舒適的頁(yè)面切換動(dòng)畫(huà),比如這樣的

Android 里一般稱之為共享元素(shareElement)動(dòng)畫(huà),也就是動(dòng)畫(huà)前后有一個(gè)(或多個(gè))相同的元素,起到前后過(guò)渡的效果,可以很清楚的看到元素的變化過(guò)程,而并不是簡(jiǎn)單的消失和出現(xiàn)。

現(xiàn)在,web 中(Chrome 111+)也迎來(lái)了這樣一個(gè)特性,叫做視圖轉(zhuǎn)換動(dòng)畫(huà) View Transitions[1],又稱“轉(zhuǎn)場(chǎng)動(dòng)畫(huà)”,也能很輕松的實(shí)現(xiàn)這類(lèi)效果,一起了解一下吧!

一、快速認(rèn)識(shí) View Transition

先從一個(gè)簡(jiǎn)單的例子來(lái)認(rèn)識(shí)一下。

比如,下面有一個(gè)網(wǎng)格列表:

<div class="list" id="list">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
  <div class="item">6</div>
  <div class="item">7</div>
  <div class="item">8</div>
  <div class="item">9</div>
  <div class="item">10</div>
</div>

簡(jiǎn)單修飾后如下:

然后我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單交互,點(diǎn)擊每個(gè)元素,元素就會(huì)被刪除。

list.addEventListener('click', function(ev){
  if (ev.target.className === 'item') {
    ev.target.remove()
  }
})

可以得到這樣的效果。

功能正常,就是有點(diǎn)太過(guò)生硬了。

現(xiàn)在輪到 View Transitions 出場(chǎng)了!我們只需要在改變狀態(tài)的地方添加document.startViewTransition,如下:

list.addEventListener('click', function(ev){
  if (ev.target.className === 'item') {
    document.startViewTransition(() => { // 開(kāi)始視圖變換
      ev.target.remove()
    });
  }
})

當(dāng)然為了兼容不支持的瀏覽器,可以做一下判斷。

list.addEventListener('click', function(ev){
  if (document.startViewTransition) { // 如果支持就視圖變換
    document.startViewTransition(() => { // 開(kāi)始視圖變換
      ev.target.remove()
    });
  } else { // 不支持就執(zhí)行原來(lái)的邏輯
    ev.target.remove()
  }
})

現(xiàn)在效果如下:

刪除前后現(xiàn)在有一個(gè)淡入淡出的效果了,也就是默認(rèn)的動(dòng)畫(huà)效果,我們可以把這個(gè)動(dòng)畫(huà)時(shí)長(zhǎng)設(shè)置大一點(diǎn),如下:

::view-transition-old(root), /* 舊視圖*/
::view-transition-new(root) { /* 新視圖*/
  animation-duration: 2s;
}

這兩個(gè)偽元素我們后面再做介紹,先看效果:

是不是明顯感覺(jué)過(guò)渡變慢了許多?

但是這種動(dòng)畫(huà)還是不夠舒服,是一種整體的變化,看不出刪除前后元素的位置變化。

接下來(lái)我們給每個(gè)元素指定一個(gè)標(biāo)識(shí),用來(lái)標(biāo)記變化前后的狀態(tài),為了方便控制,可以借助 CSS 變量。

<div class="list" id="list">
  <div class="item" style="--i: a1">1</div>
  <div class="item" style="--i: a2">2</div>
  <div class="item" style="--i: a3">3</div>
  <div class="item" style="--i: a4">4</div>
  <div class="item" style="--i: a5">5</div>
  <div class="item" style="--i: a6">6</div>
  <div class="item" style="--i: a7">7</div>
  <div class="item" style="--i: a8">8</div>
  <div class="item" style="--i: a9">9</div>
  <div class="item" style="--i: a10">10</div>
</div>

這里通過(guò)view-transition-name來(lái)設(shè)置名稱。

.item{
  view-transition-name: var(--i);
}

然后可以得到這樣的效果,每個(gè)元素在變化前后會(huì)自動(dòng)找到之前的位置,并且平滑的移動(dòng)過(guò)去,如下:

完整代碼可以查看

  • view-transition sort (juejin.cn)[2]
  • view-transition sort (codepen.io)[3]

是不是非常絲滑?這就是 View Transitions 的魅力!

二、View Transition 的核心概念

為啥僅僅加了一點(diǎn)點(diǎn)代碼就是實(shí)現(xiàn)了如此順暢的動(dòng)畫(huà)呢?為啥瀏覽器可以知道前后的元素位置關(guān)系呢?這里簡(jiǎn)單介紹一下變化原理。

整個(gè) JS 部分只有一行核心代碼,也就是document.startViewTransition,表示開(kāi)始視圖變換。

document.startViewTransition(() => {
  // 變化操作
});

整個(gè)過(guò)程包括 3 部分

  • 調(diào)用document.startViewTransition,瀏覽器會(huì)捕捉當(dāng)前頁(yè)面的狀態(tài),類(lèi)似于實(shí)時(shí)截圖,或者“快照”。
  • 執(zhí)行實(shí)際的 dom 變化,再次記錄變化后的頁(yè)面狀態(tài)(截圖)。
  • 觸發(fā)兩者的過(guò)渡動(dòng)畫(huà),包括透明度、位移等變化,也可以自定義 CSS 動(dòng)畫(huà)。

下面是一個(gè)示意圖:

在動(dòng)畫(huà)執(zhí)行的過(guò)程中,還會(huì)在頁(yè)面根節(jié)點(diǎn)自動(dòng)創(chuàng)建以下偽元素。

::view-transition
└─ ::view-transition-group(root)
   └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)

下面是控制臺(tái)的截圖。

其中,::view-transition-old表示「舊視圖」的狀態(tài),也就是變化之前的截圖,::view-transition-new表示「新視圖」的狀態(tài),也就是變化之后的截圖。

默認(rèn)情況下,整個(gè)頁(yè)面root都會(huì)作為一個(gè)狀態(tài),也就是上面的::view-transition-group(root),在切換前后會(huì)執(zhí)行淡入淡出動(dòng)畫(huà),如下:

:root::view-transition-new(root) {
  animation-name: -ua-view-transition-fade-in; /*淡入動(dòng)畫(huà)*/
}
:root::view-transition-old(root) {
  animation-name: -ua-view-transition-fade-out; /*淡出動(dòng)畫(huà)*/
}

這也是為什么在使用了document.startViewTransition后整個(gè)頁(yè)面會(huì)有淡入淡出的效果了。

為了讓每個(gè)元素都有自己的過(guò)渡狀態(tài),這里需要給每個(gè)元素都指定名稱。

.item{
  view-transition-name: item-1;
}

這樣指定名稱后,每個(gè)名稱都會(huì)創(chuàng)建一個(gè)::view-transition-group,表示獨(dú)立的分組。

這樣在變化前后view-transition-name相同的部分就會(huì)一一自動(dòng)執(zhí)行過(guò)渡動(dòng)畫(huà)了,以第4、5個(gè)元素為例(在3刪除以后)。

最后就會(huì)得到這樣的效果。

核心概念就這些了,下面再來(lái)看幾個(gè)例子。

三、不同元素之間的過(guò)渡

視圖變化其實(shí)和元素是否相同沒(méi)有關(guān)聯(lián),有關(guān)聯(lián)的只有view-transition-name,瀏覽器是根據(jù)view-transition-name尋找的,也就是相同名稱的元素在前后會(huì)有一個(gè)過(guò)渡動(dòng)畫(huà)。

比如下面這樣一個(gè)例子。

每個(gè)按鈕在打開(kāi)彈窗時(shí),都可以清楚的看到彈窗是從哪里出現(xiàn)的,如何實(shí)現(xiàn)這樣的效果呢?

從本質(zhì)上看,其實(shí)就是「按鈕到彈窗的視圖變換」,按照前面講到的,可能會(huì)想到給前后加上相同的view-transition-name,下面是HTML結(jié)構(gòu)。

<div class="bnt-group" id="group">
  <button>按鈕1</button>
  <button>按鈕2</button>
  <button>按鈕3</button>
</div>
<dialog id="dialog">
  <form method="dialog">
    我是彈窗
    <button>關(guān)閉</button>
  </form>
</dialog>

嘗試一下。

button,dialog{
  view-transition-name: dialog;
}

然后添加點(diǎn)擊打開(kāi)彈窗事件。

group.addEventListener('click', function(ev){
  if (ev.target.tagName === 'BUTTON') {
    if (document.startViewTransition) {
      document.startViewTransition(() => {
        dialog.showModal()
      });
    } else {
      dialog.showModal()
    }
  }
})

這樣會(huì)有什么問(wèn)題嗎?運(yùn)行如下:

很明顯報(bào)錯(cuò)了,意思就是一個(gè)頁(yè)面中不能有相同的view-transition-name。嚴(yán)格來(lái)講,是「不能同時(shí)出現(xiàn)」,如果其他元素都是隱藏的,只有一個(gè)是顯示的,也沒(méi)有問(wèn)題。其實(shí)仔細(xì)想一下,也很好理解,如果同時(shí)有兩個(gè)相同的名稱,并且都可見(jiàn),最后變換的時(shí)候該以哪一個(gè)為準(zhǔn)呢?

所以,在這種情況下,正確的做法應(yīng)該是動(dòng)態(tài)設(shè)置view-transition-name,比如默認(rèn)不給按鈕添加名稱,只有在點(diǎn)擊的時(shí)候才添加,然后在變換結(jié)束之后再移除按鈕的view-transition-name,實(shí)現(xiàn)如下:

group.addEventListener('click', function(ev){
  if (ev.target.tagName === 'BUTTON') {
    ev.target.style.viewTransitionName = 'dialog' // 動(dòng)態(tài)添加 viewTransitionName
    if (document.startViewTransition) {
      document.startViewTransition(() => {
        ev.target.style.viewTransitionName = '' // 結(jié)束后移除 viewTransitionName
        dialog.showModal()
      });
    } else {
      dialog.showModal()
    }
  }
})

這樣就實(shí)現(xiàn)了動(dòng)態(tài)縮放的效果:

大致已經(jīng)實(shí)現(xiàn)想要的效果,不過(guò)還有一個(gè)小問(wèn)題,我們把速度放慢一點(diǎn)(把動(dòng)畫(huà)時(shí)長(zhǎng)設(shè)置長(zhǎng)一點(diǎn))。

可以清楚的看到,原本的按鈕先放大到了彈窗大小,然后逐漸消失。這個(gè)過(guò)程是我們不需要的,有沒(méi)有辦法去掉呢?

當(dāng)然也是可以的!原本的按鈕其實(shí)就是舊視圖,也就是點(diǎn)擊之前的截圖,我們只需要將這個(gè)視圖隱藏起來(lái)就行了。

::view-transition-old(dialog) {
  display: none;
}

這樣就完美實(shí)現(xiàn)了從哪里點(diǎn)擊就從哪里打開(kāi)的效果:

完整代碼可以查看:

  • view-transition-dialog (juejin.cn)[4]
  • view-transition-dialog (codepen.io)[5]

四、自定義過(guò)渡動(dòng)畫(huà)

通過(guò)前面的例子可以看出,默認(rèn)情況下,視圖轉(zhuǎn)換動(dòng)畫(huà)是一種淡入淡出的動(dòng)畫(huà),然后還有如果位置、大小不同,也會(huì)平滑過(guò)渡。

除此之外,我們還可以手動(dòng)指定過(guò)渡動(dòng)畫(huà)。比如下面這個(gè)例子。

這是一個(gè)黑暗模式的簡(jiǎn)易模型,實(shí)現(xiàn)也非常簡(jiǎn)單,準(zhǔn)備兩套主題,這里用color-scheme實(shí)現(xiàn)。

.dark{
  color-scheme: dark;
}

然后通過(guò)點(diǎn)擊動(dòng)態(tài)給html切換dark類(lèi)名。

btn.addEventListener('click', function(ev){
  document.documentElement.classList.toggle('dark')
})

這樣就得到了主題切換效果:

接著,我們添加視圖轉(zhuǎn)換動(dòng)畫(huà)。

btn.addEventListener('click', function(ev){
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      document.documentElement.classList.toggle('dark')
    });
  } else {
    document.documentElement.classList.toggle('dark')
  }
})

這樣就得到了一個(gè)默認(rèn)的淡入淡出的切換效果(為了方便觀察,將動(dòng)畫(huà)時(shí)長(zhǎng)延長(zhǎng)了)。

圖片

你可以把前后變化想象成是兩張截圖的變化,如果要實(shí)現(xiàn)點(diǎn)擊出現(xiàn)圓形裁剪動(dòng)畫(huà),其實(shí)就是在新視圖上執(zhí)行一個(gè)裁剪動(dòng)畫(huà),由于是完全重疊的,所以看著像是一種穿透擴(kuò)散的效果:

動(dòng)畫(huà)很簡(jiǎn)單,就是一個(gè)clip-path動(dòng)畫(huà)。

@keyframes clip {
  from {
    clip-path: circle(0%);
  }
  to{
    clip-path: circle(100%);
  }
}

我們把這個(gè)動(dòng)畫(huà)放在::view-transition-new中。

::view-transition-new(root) {
  /* mix-blend-mode: normal; */
  animation: clip .5s ease-in;
  /* animation-duration: 2s; */
}

效果如下:

是不是還有點(diǎn)奇怪?這是因?yàn)槟J(rèn)的一些樣式導(dǎo)致,包括原有的淡出效果,還有混合模式。

所以還需要去除這些影響。

::view-transition-old(root) {
  animation: none;
}
::view-transition-new(root) {
  mix-blend-mode: normal;
  animation: clip .5s ease-in;
}

當(dāng)然你可以把鼠標(biāo)點(diǎn)擊的位置傳遞到頁(yè)面根節(jié)點(diǎn)。

btn.addEventListener('click', function(ev){
  document.documentElement.style.setProperty('--x', ev.clientX + 'px')
  document.documentElement.style.setProperty('--y', ev.clientY + 'px')
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      document.documentElement.classList.toggle('dark')
    });
  } else {
    document.documentElement.classList.toggle('dark')
  }
})

動(dòng)畫(huà)里直接通過(guò)CSS 變量獲取。

@keyframes clip {
  from {
    clip-path: circle(0% at var(--x) var(--y));
  }
  to{
    clip-path: circle(100% at var(--x) var(--y));
  }
}

這樣就實(shí)現(xiàn)了完美的擴(kuò)散切換效果:

完整代碼可以查看:

  • view transition theme change - (juejin.cn)[6]
  • view transition theme change (codepen.io)[7]

五、其他案例

找了幾個(gè)有趣的例子。

只要涉及到前后過(guò)渡變化的,都可以考慮用這個(gè)特性,例如下面的拖拽排序。

Android https://mp.weixin.qq.com/s/v8XwlqLAtCxxYG2FOvatFw。

https://codepen.io/argyleink/pen/rNQZbLr。

再比如這樣一個(gè)數(shù)字過(guò)渡動(dòng)畫(huà)。

https://mp.weixin.qq.com/s/v8XwlqLAtCxxYG2FOvatFw

https://codepen.io/argyleink/pen/jOQKdeW

還有類(lèi)似于 APP 的轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(多頁(yè)面跳轉(zhuǎn))

https://simple-set-demos.glitch.me/6-expanding-image/。

五、總結(jié)和說(shuō)明

總的來(lái)說(shuō),原生的視圖轉(zhuǎn)換動(dòng)畫(huà)可以很輕松的實(shí)現(xiàn)兩種狀態(tài)的過(guò)渡,讓 web 也能實(shí)現(xiàn)媲美原生 APP 的動(dòng)畫(huà)體驗(yàn),下面再來(lái)回顧一下整個(gè)變化過(guò)程:

  • 調(diào)用document.startViewTransition,瀏覽器會(huì)捕捉當(dāng)前頁(yè)面的狀態(tài),類(lèi)似于實(shí)時(shí)截圖,或者“快照”。
  • 執(zhí)行實(shí)際的 dom 變化,再次記錄變化后的頁(yè)面狀態(tài)(截圖)。
  • 觸發(fā)兩者的過(guò)渡動(dòng)畫(huà),包括透明度、位移等變化,也可以自定義 CSS 動(dòng)畫(huà)。
  • 默認(rèn)情況下是整個(gè)頁(yè)面的淡入淡出變化。
  • ::view-transition-old表示「舊視圖」的狀態(tài),也就是變化之前的截圖,::view-transition-new表示「新視圖」的狀態(tài),也就是變化之后的截圖。
  • 如果需要指定具體元素的變化,可以給該元素指定view-transition-name。
  • 前后變化不一定要同一元素,瀏覽器是根據(jù)view-transition-name尋找的。
  • 同一時(shí)間頁(yè)面上不能出現(xiàn)兩個(gè)相同view-transition-name的元素,不然視圖變化會(huì)失效。

另外,視圖轉(zhuǎn)換動(dòng)畫(huà)應(yīng)該作為一種「體驗(yàn)增強(qiáng)」的功能,而非必要功能,在使用動(dòng)畫(huà)時(shí)其實(shí)拖慢了頁(yè)面打開(kāi)或者更新的速度,并且在動(dòng)畫(huà)過(guò)程中,頁(yè)面是完全“凍結(jié)”的,做不了任何事情,因此需要衡量好動(dòng)畫(huà)的時(shí)間,如果頁(yè)面本身就很慢就更不要使用這些動(dòng)畫(huà)了。

[1]View Transitions: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

[2]view-transition sort (juejin.cn): https://code.juejin.cn/pen/7268263402853072931

[3]view-transition sort (codepen.io): https://codepen.io/xboxyan/pen/BavBevP

[4]view-transition-dialog (juejin.cn): https://code.juejin.cn/pen/7268262983178911779

[5]view-transition-dialog (codepen.io): https://codepen.io/xboxyan/pen/WNLeBgY

[6]view transition theme change - (juejin.cn): https://code.juejin.cn/pen/7268257573277532219

[7]view transition theme change (codepen.io): https://codepen.io/xboxyan/pen/poqzmLY

責(zé)任編輯:姜華 來(lái)源: 前端偵探
相關(guān)推薦

2023-09-27 08:50:57

Serverles編寫(xiě)運(yùn)維

2025-03-12 14:09:56

2020-12-21 05:56:54

Clipboard A復(fù)制圖像開(kāi)發(fā)技術(shù)

2023-04-23 09:01:43

CSS動(dòng)畫(huà)合成

2023-04-24 09:23:31

CSS動(dòng)畫(huà)合成

2020-02-10 14:26:10

GitHub代碼倉(cāng)庫(kù)

2022-03-24 13:36:18

Java悲觀鎖樂(lè)觀鎖

2020-12-10 08:44:35

WebSocket輪詢Comet

2020-03-01 17:53:38

Excel大數(shù)據(jù)微軟

2018-06-05 17:40:36

人工智能語(yǔ)音識(shí)別

2024-04-11 12:19:01

Rust數(shù)據(jù)類(lèi)型

2019-02-20 14:16:43

2019-03-11 14:33:21

Redis內(nèi)存模型數(shù)據(jù)庫(kù)

2023-03-02 08:00:55

包管理工具pnpm 包

2018-04-25 06:46:52

2022-03-07 06:34:22

CQRS數(shù)據(jù)庫(kù)數(shù)據(jù)模型

2022-12-30 08:31:27

MDC查詢?nèi)罩?/a>

2018-07-17 14:42:50

2023-11-18 09:09:08

GNUBSD協(xié)議

2024-02-28 18:22:13

AI處理器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)