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

FLIP,一種高端優(yōu)雅但簡單易用的前端動(dòng)畫思維

開發(fā) 前端
FLIP 是四個(gè)單詞的首字母,F(xiàn)irst、Last、Invert、Play,這四個(gè)單詞給我們提供了完成動(dòng)畫的具體思路。First 表示元素初始時(shí)的具體信息,在 html 環(huán)境中,這個(gè)事情是比較容易就能做到的,我們可以利用 getBoundingClientRect? 或者 getComputedStyle 來拿到元素的初始信息。

有一種能夠快速實(shí)現(xiàn)復(fù)雜動(dòng)畫交互的動(dòng)畫思維 FLIP,為了介紹這個(gè)動(dòng)畫思維,我準(zhǔn)備了三個(gè)案例

一、FLIP

FLIP 是四個(gè)單詞的首字母,F(xiàn)irst、Last、Invert、Play,這四個(gè)單詞給我們提供了完成動(dòng)畫的具體思路。

First 表示元素初始時(shí)的具體信息,在 html 環(huán)境中,這個(gè)事情是比較容易就能做到的,我們可以利用 getBoundingClientRect 或者 getComputedStyle 來拿到元素的初始信息。

Last 表示元素結(jié)束時(shí)的位置信息。此時(shí)我們可以直接改變元素的位置,把元素放到新的節(jié)點(diǎn)上去。這樣我們就可以直接使用同樣的方式拿到結(jié)束時(shí)的元素具體信息。

Invert 表示倒置。雖然元素到了結(jié)束時(shí)的節(jié)點(diǎn)位置,但是視覺上我們并沒有看到,此時(shí)要設(shè)計(jì)讓元素動(dòng)畫從 First 通過動(dòng)畫的方式變換到 Last,剛好我們又記錄了動(dòng)畫的開始和結(jié)束信息,因此我們可以利用自己熟悉的動(dòng)畫方式來完成 Invert。

Play 表示動(dòng)畫開始執(zhí)行。在代碼上通常 Invert 表示傳參,Play 表示具體的動(dòng)畫執(zhí)行。

接下來我們使用三個(gè)案例來進(jìn)一步學(xué)習(xí)這個(gè)動(dòng)畫思想。

二、案例一:元素 X 軸位置隨機(jī)變化

案例效果如圖所示。

案例的 html 結(jié)構(gòu)如下:

<div id="container">
  <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>
<button id="sort">隨機(jī)排序</button>

先獲取兩個(gè)關(guān)鍵 DOM 對象。

const container = document.getElementById('container')
const sortBtn = document.getElementById('sort')

First,記錄元素初始位置信息。此時(shí)我們把開始的 X 位置信息保存在子節(jié)點(diǎn)對象上,我們也可以單獨(dú)另起一個(gè)數(shù)組來保存所有子節(jié)點(diǎn)的具體信息。

// 記錄開始位置信息
function record(container) {
  const all = [...container.children]
  all.forEach((item, i) => {
    const rect = item.getBoundingClientRect()
    item.startX = rect.left
  })
}

Last,直接改變元素的節(jié)點(diǎn)位置。因?yàn)楦淖冎?,元素在新的?jié)點(diǎn)上,那么我們這里就可以單獨(dú)快捷獲取元素改變之后的位置信息,所以可以封裝一個(gè)方法,只改變元素的節(jié)點(diǎn)位置信息,而在需要的時(shí)候獲取 Last 即可。

當(dāng)然也可以單獨(dú)在這一步把屬性位置信息保存起來。

function change() {
  const all = [...container.children]
  const len = all.length
  all.forEach((item, i) => {
    const newIndex = Math.floor(Math.random() * len)
    if (newIndex !== i) {
      const nextDOM = item.nextElementSibling
      container.insertBefore(item, all[newIndex])
      container.insertBefore(all[newIndex], nextDOM)
    }
  })
}

Invert 和 play 在代碼實(shí)現(xiàn)上往往會(huì)耦合在一起,Invert 表示參數(shù)傳入,play 表示動(dòng)畫執(zhí)行。因此我們可以最后再定義一個(gè)方法 play 表示動(dòng)畫的執(zhí)行。

function play(container) {
  const all = [...container.children]
  const len = all.length
  all.forEach((item, i) => {
    const rect = item.getBoundingClientRect()
    const currentX = rect.left
    item.animate([
      { transform: `translateX(${item.startX - currentX}px)` },
      { transform: 'translateX(0px)' }
    ], {duration: 600})
  })
}

這里我使用了一個(gè) DOM 元素自帶的 animate 方法,來完成動(dòng)畫的實(shí)現(xiàn),該方法目前還是一個(gè)實(shí)驗(yàn)性的 api,在 2022 年提出,目前最新版的 chrome 瀏覽器已經(jīng)支持。

該動(dòng)畫接口使用起來也比較簡單,跟 keyframes 類似。

animate(keyframes, options)

keyframes 表示關(guān)鍵幀數(shù)組,options 表示動(dòng)畫持續(xù)時(shí)間,或者包含多個(gè)時(shí)間屬性,用于配置動(dòng)畫函數(shù)或者 iterations、delay 等常見屬性,與 css 的動(dòng)畫屬性基本保持一致。

你也可以自己封裝一個(gè)類似的方法,或者使用成熟的第三方工具庫,能達(dá)到類似效果的方式也比較多。

然后在點(diǎn)擊按鈕時(shí),執(zhí)行即可。

sortBtn.onclick = () => {
  record(container)
  change()
  play(container)
}

三、案例二:多屬性變化

案例效果展示如圖:

元素多屬性動(dòng)畫并不會(huì)增加多少實(shí)現(xiàn)復(fù)雜度,只是多記錄幾個(gè)元素而已。這個(gè)案例包含了 x/y/backgroundColor 三個(gè)屬性。

First,記錄初始信息。

// 記錄開始位置信息
function record(container) {
  const all = [...container.children]
  all.forEach((item, i) => {
    const rect = item.getBoundingClientRect()
    item.startX = rect.left
    item.startY = rect.top
    item.bgColor = getComputedStyle(item)['backgroundColor']
  })
}

Last,直接改變元素節(jié)點(diǎn)位置。

因?yàn)楦淖児?jié)點(diǎn)位置之后,能夠輕易獲取到元素新的位置的具體屬性,所以這一步可以稱之為 Last。

function change() {
  const all = [...container.children]
  const len = all.length
  all.forEach((item, i) => {
    const newIndex = Math.floor(Math.random() * len)
    if (newIndex !== i) {
      const nextDOM = item.nextElementSibling
      container.insertBefore(item, all[newIndex])
      container.insertBefore(all[newIndex], nextDOM)
    }
  })
}

Invert and Play。

function play(container) {
  const all = [...container.children]
  const len = all.length
  all.forEach((item, i) => {
    const rect = item.getBoundingClientRect()
    const currentX = rect.left
    const currentY = rect.top
    const bgColor = getComputedStyle(item, false)["backgroundColor"]
    item.animate([
      { transform: `translate(${item.startX - currentX}px, ${item.startY - currentY}px)`, backgroundColor: item.bgColor },
      { transform: 'translate(0px, 0px)', backgroundColor: bgColor }
    ], {duration: 600})
  })
}

最后,點(diǎn)擊執(zhí)行。

sortBtn.onclick = () => {
  record(container)
  change()
  play(container)
}

四、案例三:共享元素動(dòng)畫

上面那兩個(gè)案例,在實(shí)踐中基本上沒什么用,主要用于輔助學(xué)習(xí)。因此大家可能對于高級感和優(yōu)雅感的體會(huì)不是那么深刻。

第三個(gè)案例則以在實(shí)踐中,在前端很少有項(xiàng)目能夠做到的共享元素動(dòng)畫,來為大家介紹這種動(dòng)畫思想方案的厲害之處。

共享元素動(dòng)畫在前端是一個(gè)很少被提及的概念,但是在客戶端的開發(fā)中,卻已經(jīng)運(yùn)用非常廣泛。

對于前端而言,這代表了未來頁面交互的主要發(fā)展方向。例如在小紅書的 web 端已經(jīng)實(shí)現(xiàn)了該功能。

在 FLIP 的指導(dǎo)思想下,該功能實(shí)現(xiàn)起來也并不復(fù)雜。

First,記錄元素的初始信息。

const all = [...list.children]
// 記錄開始位置信息
all.forEach((item, i) => {
  const rect = item.getBoundingClientRect()
  item.startX = rect.left
  item.startY = rect.top
  item.width = rect.width
  item.height = rect.height
})

當(dāng)我們點(diǎn)擊元素時(shí),此時(shí)有兩個(gè)元素位置信息在發(fā)生變化,一個(gè)是背景彈窗。他的變化比較簡單,就是透明度的變化,因此我們不用記錄他的信息。另外一個(gè)就是共享的元素 item,此時(shí)我們記錄了四個(gè)信息:startX、startY、width、height。

Last,點(diǎn)擊元素之后,出現(xiàn)彈窗。此時(shí)我們把相關(guān)的兩個(gè)節(jié)點(diǎn)插入到正確的位置上即可。

function change(element) {
  current = element.cloneNode(true)
  modal = document.createElement('div')

  modal.id = 'modal'
  modal.appendChild(current)
  document.body.appendChild(modal)
}

Invert and Play. 也是比較簡單,就是獲取新節(jié)點(diǎn)的位置,然后設(shè)置動(dòng)畫即可。

function play(preItem) {
  modal.animate([
    {backgroundColor: `rgba(0, 0, 0, 0)`},
    {backgroundColor: `rgba(0, 0, 0, ${0.3})`}
  ], {duration: 600})

  const rect = current.getBoundingClientRect()
  const currentX = rect.left
  const currentY = rect.top
  const width = rect.width
  const height = rect.height

  const x = preItem.startX - currentX - (width - preItem.width) / 2
  const y = preItem.startY - currentY - (height - preItem.height) / 2

  console.log(x, y)

  current.animate([
    {
      transform: `translate(${x}px, ${y}px)`,
      width: `${preItem.width}px`,
      height: `${preItem.height}px`
    },
    {
      transform: 'translate(0px, 0px)',
      height: `${height}px`,
      width: `${width}px`
    }
  ], {duration: 600})
}

最后給每個(gè)元素添加點(diǎn)擊事件。

all.forEach((item, i) => {
  item.onclick = (event) => {
    change(event.target)
    play(event.target)
  }
})

彈窗上也需要新增一個(gè)點(diǎn)擊事件,用于執(zhí)行彈窗消失的動(dòng)畫。

modal.onclick = () => {
  const ani = modal.animate([
    {backgroundColor: `rgba(0, 0, 0, ${0.3})`},
    {backgroundColor: `rgba(0, 0, 0, 0)`}
  ], {duration: 600})

  const rect = current.getBoundingClientRect()
  const currentX = rect.left
  const currentY = rect.top
  const width = rect.width
  const height = rect.height

  const x = element.startX - currentX - (width - element.width) / 2
  const y = element.startY - currentY - 100

  current.animate([
    {
      transform: 'translate(0px, 0px)',
      height: `${height}px`,
      width: `${width}px`
    },
    {
      transform: `translate(${x}px, ${y}px)`,
      width: `${element.width}px`,
      height: `${element.height}px`
    },
  ], {duration: 600})

  console.log(x, y)

  ani.onfinish = () => {
    modal.remove()
  }
}

并在運(yùn)動(dòng)結(jié)束之后,刪除彈窗節(jié)點(diǎn)。

ani.onfinish = () => {
  modal.remove()
}

一個(gè)共享元素動(dòng)畫,就這么簡單的實(shí)現(xiàn)了。

五、共享元素動(dòng)畫擴(kuò)展思考

如果我們要結(jié)合路由切換轉(zhuǎn)場來實(shí)現(xiàn)共享元素動(dòng)畫,其實(shí)實(shí)現(xiàn)原理也是一樣的,非常的簡單,我們只需要在路由切換時(shí),把共享元素的初始位置信息記錄下來并作為參數(shù)傳遞給下一個(gè)頁面即可。

也就是說,我們只需要把這里的兩個(gè)點(diǎn)擊事件,結(jié)合路由事件和參數(shù)傳遞,就能做到跟小紅書一樣的共享元素路由轉(zhuǎn)場效果。

不過至于如何封裝讓代碼更加簡潔,本文就不再擴(kuò)展啦,交給大家自己思考。

責(zé)任編輯:姜華 來源: 這波能反殺
相關(guān)推薦

2024-05-09 08:20:29

AC架構(gòu)數(shù)據(jù)庫冗余存儲(chǔ)

2020-03-04 17:03:10

數(shù)據(jù)分析思維說明

2022-02-25 14:42:09

OpenHarmon環(huán)境搭建鴻蒙

2022-06-06 15:44:24

大數(shù)據(jù)數(shù)據(jù)分析思維模式

2010-03-26 13:34:47

CentOS安裝

2020-12-16 10:12:52

大數(shù)據(jù)小數(shù)據(jù)人工智能

2022-03-01 09:58:10

高并發(fā)架構(gòu)開發(fā)

2011-02-25 13:52:18

Proftpd管理

2011-02-25 13:52:18

Proftpd管理

2016-12-23 21:11:05

深度學(xué)習(xí)思維方式大數(shù)據(jù)

2011-07-04 10:17:38

JDBC

2021-10-26 16:49:34

系統(tǒng)性能定位

2017-08-24 15:02:01

前端增量式更新

2020-12-23 10:10:23

Pythonweb代碼

2022-06-22 09:44:41

Python文件代碼

2022-07-07 10:33:27

Python姿勢代碼

2020-12-09 10:15:34

Pythonweb代碼

2016-09-20 12:49:29

2011-04-06 10:09:56

MySQL數(shù)據(jù)庫安裝

2010-05-27 11:04:32

點(diǎn)贊
收藏

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