成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

FLIP,一種高端優雅但簡單易用的前端動畫思維

開發 前端
FLIP 是四個單詞的首字母,First、Last、Invert、Play,這四個單詞給我們提供了完成動畫的具體思路。First 表示元素初始時的具體信息,在 html 環境中,這個事情是比較容易就能做到的,我們可以利用 getBoundingClientRect? 或者 getComputedStyle 來拿到元素的初始信息。

有一種能夠快速實現復雜動畫交互的動畫思維 FLIP,為了介紹這個動畫思維,我準備了三個案例

一、FLIP

FLIP 是四個單詞的首字母,First、Last、Invert、Play,這四個單詞給我們提供了完成動畫的具體思路。

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

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

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

Play 表示動畫開始執行。在代碼上通常 Invert 表示傳參,Play 表示具體的動畫執行。

接下來我們使用三個案例來進一步學習這個動畫思想。

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

案例效果如圖所示。

案例的 html 結構如下:

<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">隨機排序</button>

先獲取兩個關鍵 DOM 對象。

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

First,記錄元素初始位置信息。此時我們把開始的 X 位置信息保存在子節點對象上,我們也可以單獨另起一個數組來保存所有子節點的具體信息。

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

Last,直接改變元素的節點位置。因為改變之后,元素在新的節點上,那么我們這里就可以單獨快捷獲取元素改變之后的位置信息,所以可以封裝一個方法,只改變元素的節點位置信息,而在需要的時候獲取 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 和 play 在代碼實現上往往會耦合在一起,Invert 表示參數傳入,play 表示動畫執行。因此我們可以最后再定義一個方法 play 表示動畫的執行。

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})
  })
}

這里我使用了一個 DOM 元素自帶的 animate 方法,來完成動畫的實現,該方法目前還是一個實驗性的 api,在 2022 年提出,目前最新版的 chrome 瀏覽器已經支持。

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

animate(keyframes, options)

keyframes 表示關鍵幀數組,options 表示動畫持續時間,或者包含多個時間屬性,用于配置動畫函數或者 iterations、delay 等常見屬性,與 css 的動畫屬性基本保持一致。

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

然后在點擊按鈕時,執行即可。

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

三、案例二:多屬性變化

案例效果展示如圖:

元素多屬性動畫并不會增加多少實現復雜度,只是多記錄幾個元素而已。這個案例包含了 x/y/backgroundColor 三個屬性。

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,直接改變元素節點位置。

因為改變節點位置之后,能夠輕易獲取到元素新的位置的具體屬性,所以這一步可以稱之為 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})
  })
}

最后,點擊執行。

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

四、案例三:共享元素動畫

上面那兩個案例,在實踐中基本上沒什么用,主要用于輔助學習。因此大家可能對于高級感和優雅感的體會不是那么深刻。

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

共享元素動畫在前端是一個很少被提及的概念,但是在客戶端的開發中,卻已經運用非常廣泛。

對于前端而言,這代表了未來頁面交互的主要發展方向。例如在小紅書的 web 端已經實現了該功能。

在 FLIP 的指導思想下,該功能實現起來也并不復雜。

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
})

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

Last,點擊元素之后,出現彈窗。此時我們把相關的兩個節點插入到正確的位置上即可。

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

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

Invert and Play. 也是比較簡單,就是獲取新節點的位置,然后設置動畫即可。

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})
}

最后給每個元素添加點擊事件。

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

彈窗上也需要新增一個點擊事件,用于執行彈窗消失的動畫。

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()
  }
}

并在運動結束之后,刪除彈窗節點。

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

一個共享元素動畫,就這么簡單的實現了。

五、共享元素動畫擴展思考

如果我們要結合路由切換轉場來實現共享元素動畫,其實實現原理也是一樣的,非常的簡單,我們只需要在路由切換時,把共享元素的初始位置信息記錄下來并作為參數傳遞給下一個頁面即可。

也就是說,我們只需要把這里的兩個點擊事件,結合路由事件和參數傳遞,就能做到跟小紅書一樣的共享元素路由轉場效果。

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

責任編輯:姜華 來源: 這波能反殺
相關推薦

2025-05-23 10:20:00

2024-05-09 08:20:29

AC架構數據庫冗余存儲

2022-02-25 14:42:09

OpenHarmon環境搭建鴻蒙

2020-03-04 17:03:10

數據分析思維說明

2022-06-06 15:44:24

大數據數據分析思維模式

2010-03-26 13:34:47

CentOS安裝

2020-12-16 10:12:52

大數據小數據人工智能

2022-03-01 09:58:10

高并發架構開發

2011-02-25 13:52:18

Proftpd管理

2011-02-25 13:52:18

Proftpd管理

2016-12-23 21:11:05

深度學習思維方式大數據

2021-10-26 16:49:34

系統性能定位

2011-07-04 10:17:38

JDBC

2017-08-24 15:02:01

前端增量式更新

2020-12-09 10:15:34

Pythonweb代碼

2020-12-23 10:10:23

Pythonweb代碼

2022-07-07 10:33:27

Python姿勢代碼

2022-06-22 09:44:41

Python文件代碼

2016-09-20 12:49:29

2011-04-06 10:09:56

MySQL數據庫安裝
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91麻豆久久久| 欧美成人精品一区二区男人看 | 五月婷婷在线播放 | 国产高清性xxxxxxxx | 国产精品视频www | 日韩高清中文字幕 | 亚洲a毛片 | 久草青青| 国产伦一区二区三区四区 | 亚洲午夜精品久久久久久app | 精品成人佐山爱一区二区 | 波波电影院一区二区三区 | 日本黄色影片在线观看 | 成人一区av | 国产成人精品一区二三区在线观看 | 久久av一区二区三区 | 日本一区二区影视 | 亚洲日本欧美日韩高观看 | 国产在线1区 | 美女久久 | 福利视频亚洲 | 伊人二区| 国产一区二区三区在线看 | 在线一区视频 | 日韩av第一页 | 91激情视频| 亚洲一区国产精品 | 久久一区二区三区四区五区 | 欧美一区二区三区在线观看视频 | 免费国产一区二区 | av一区二区三区四区 | 羞羞色视频 | 日本精品一区二区 | 欧美色综合一区二区三区 | 成人av播放 | 国产成在线观看免费视频 | 国产成人精品午夜视频免费 | 91精品国产91久久久久福利 | a级毛片免费高清视频 | 久久99精品久久久久 | 欧美高清视频一区 |