【譯】Vue + svg,面向想要自由的駕馭 css 動畫的人詳細解說。(附代碼)

你們好,這裏是 UX(交互體驗設計師)& 前端開發,而且還喜歡畫畫的 yuki!@yuneco。 在這篇文章裏,爲了可以更加輕鬆地去使用 Vue 和 css animation,基礎的部分,將會跟你們一步步的詳細解釋。目標以下圖↓所示,用 JavaScript 自由的控制動畫。javascript

效果圖

源碼送上 github.com/yuneco/css-…css

目錄

那麼先從最簡單的 svg 標籤開始。用 Vue 自由地配置、使其變形。而後利用 csstransition 來作動畫,最後把動畫抽象封裝,運用到更加複雜的場景上。html

  1. 製做 svg
  2. 建立 Vue 項目
  3. 顯示 svg
  4. 可以自由配置
  5. 可以更自由更大角度的變化
  6. 賦予動畫
  7. 可以連續進行動畫
  8. 抽象封裝動畫

注意點

  1. 這篇文章介紹的方法並非使用動畫時通用的方法。
  2. 想要製做更復雜的動畫,請使用 anime.js 或者 pixi.js
  3. 這篇文章並無使用專門的動畫庫,而是本身封裝的動畫,目標是爲了更加深刻地理解 Vue、javascriptcss 動畫

雖然還有不少理由,但能點亮本身的【本身組建可以理解的動畫】這方面的技能樹,無疑也是很高興的。文章稍稍有點長,若是你能看完的話,那我也很高興。前端

製做 svg

第一步,顯示這篇教程要使用到的 svg,用 Illustrator 製做本身喜歡的角色去,依次從菜單上選擇 [ファイル] -> [書き出し] -> [スクリーン用に書き出し],格式選擇 svg,從右邊齒輪同樣的圖標,顯示設定。vue

製做svg

設定看起來有點複雜的😓,這裏在 Vue 也沒有那麼麻煩的去使 用 svg,因此這裏的設定不須要太在乎,右下角的 Responsive(レスポンシブ) 選擇記得要取消掉。java

設定好了以後,「設定を保存」->「アートボードを書き出し」導出 svg 文件,沒有 Illustrator 的同窗用其它的文件也 ok。怕麻煩的同窗,我姑且在 github 上也放了一份...node

用瀏覽器打開,大概就是這種感受,名字叫 tama桑,如今剛決定的。爲了方便理解,我特地加了 1 像素的邊框。git

示例

建立 Vue 項目

無論怎麼說,不建立的 Vue 項目的話,就無法開始。運行,vue create 項目的名字 建立項目,就像下面同樣,固然,你也能夠根據本身的喜愛來。github

? Please pick a preset: 
  default (babel, eslint) 
❯ Manually select features 

? Check the features needed for your project: 
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◯ Vuex
❯◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): (Use arrow keys)
❯ Sass/SCSS (with dart-sass) 
  Sass/SCSS (with node-sass) 
  Less 
  Stylus 

? Pick a linter / formatter config: 
  ESLint with error prevention only 
  ESLint + Airbnb config 
❯ ESLint + Standard config 
  ESLint + Prettier 

以後所有默認
複製代碼

項目建立好了以後,刪除掉多餘的 HelloWorld 組件,而後在空項目裏運行 npm run serve 確承認以跑起來就行。npm

顯示 svg

剛剛作好的 svg 文件 放在 /public/img/, img 目錄沒有的話就本身建一下。接下來就先用 Vue 把 svg 顯示出來吧。

只是普通的顯示 svg 的話跟 Vue 沒有關係。另外爲了以後的考慮,先把 src/components/Tama.vue 文件建立好,代碼像下面這樣。

<template>
  <img src="/img/tama.svg" alt="タマさん">
</template>
複製代碼

這篇教程用最簡單的方法,用 img 標籤來讀取 svg。固然用其它的方法也行,好比像下面這樣:

  1. 直接使用 <svg> 標籤
  2. 使用 css 的 background-image
  3. 使用 vue-svg-loader 之類的插件來導入成 Vue 組件

固然還有各類各樣的方法,特別是方案 1,用起來特別爽(顏色和形狀能夠在 Vue 裏面控制等等),若是想作的複雜點,能夠試試這個。建好 Tama.vue 組件後呢,在 App.vue 裏面引入顯示。

// Tama.vue
<template>
  <div id="app">
    <tama></tama>
  </div>
</template>

<script> import Tama from './components/Tama.vue' export default { name: 'app', components: { Tama } } </script>

<style lang="scss"> html, body { margin: 0; padding: 0; } body { position: relative; height: 100%; background: url('/img/grid.svg') repeat; } #app { margin: 0; } </style>
複製代碼

這裏只是把 Tama 導入而已。 清空了默認樣式以後,爲了方便展現,背景用 網格 展現。 展現成下面這樣就 ok。

示例

配置到你想要的地方

畫面已經出來了,若是不能自由的展現到想要的地方,就無法作動畫。接下來就是把 tama桑 配置到任意的地方放。

配置到指定的座標

先配置到到 x = 200px, y = 100px 這個位置吧。

<template>
  <img class="tama-root" src="/img/tama.svg" alt="タマさん" >
</template>

<style lang="scss" scoped> .tama-root { position: absolute; left: 0; top: 0; transform: translate(200px, 100px); } </style>
複製代碼

position: absolute 絕對定位,而後用 transform 指定座標,雖然指定位置也能夠用 topleft,可是用 css 來作動畫的時候,儘可能仍是用 transform 吧。只是指定元素位置的話,這樣子作比較輕鬆(還有各類其它需求),而且由 GUP 渲染的話,實現的動畫可以絲般順滑。

ok,完成!tama桑 的位置成功地挪動到了 (200px, 100px)

示例

嗯?稍等😅,明明讓 tama桑 的位置顯示在 (200px, 100px) 的位置,爲何座標是以左上角爲基準,而後畫也跟着到這個位置。仍是想把她的腳底做爲基準點。

示例

作法有不少,此次就用 margin 來調整吧。

.tama-root {
  // ...
  margin: -300px auto auto -90px;
}
複製代碼

示例

用參數來控制座標

以前用的是定死的座標,在實際使用時,仍是會想要在變話座標的時候加上動畫的吧。因此固然不是在 css 寫死的,用 Vue 的 props 來控制吧。

// Tama.vue
<script> export default { name: 'Tama', props: { x: { type: Number, default: 200 }, y: { type: Number, default: 100 } } } </script>
複製代碼

Tama.vue 裏追加新代碼,加上 x, y 兩個參數,而後也把 type 和 default 指定下。 由於會用 poprs 來動態控制樣式,因此在 <style> 把 transform 刪掉。

// Tama.vue
.tama-root {
  // ...
  margin: -300px auto auto -90px;
  // 刪掉: transform: translate(200px, 100px);
}
複製代碼

而後在 template 裏指定要使用的 style。

// Tama.vue
<template>
  <img class="tama-root" src="/img/tama.svg" alt="タマさん" :style="{ transform: `translate(${x}px, ${y}px)` }" >
</template>
複製代碼

而後在引用的地方 App.vue 裏就能夠指定 tama桑 的位置了。

// APP.vue
<div id="app">
  <tama :x="300" :y="400"></tama>
</div>
複製代碼

變化角度和大小

同理,咱們來變化大小(scale)和角度吧。若是可以自由的控制位置、大小、角度,就能組合成本身想要畫面。

用屬性控制大小和角度

在相同的地方加上東西,scale 能夠指定橫向仍是縱向,因此這裏加上 scaleXscaleY 兩種屬性。

// Tama.vue
props: {
  x: { type: Number, default: 200 },
  y: { type: Number, default: 100 },
  scaleX: { type: Number, default: 1.0 },
  scaleY: { type: Number, default: 1.0 },
  rotate: { type: Number, default: 0 }
}
複製代碼

而後讓這些個屬性在 template 裏生效,scale、rotate 也是在 transfrom 裏指定,很簡單吧!scale 去掉單位, rotate 加上角度單位(deg)。

// Tama.vue
<img class="tama-root" src="/img/tama.svg" alt="タマさん" :style="{ transform: `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY}) rotate(${rotate}deg)` }" >
複製代碼

而後在引用的 App.vue 裏指定下屬性吧。

// App.vue
<div id="app">
  <tama :x="300" :y="400" :scale-x="1.5" :scale-y="1.5" :rotate="45"></tama>
</div>
複製代碼

跟指定位置同樣很簡單吧!嗯?等等...

示例

角度和大小確實是變化了,再就是基準點也有點奇怪😂

示例

爲了解決這個問題,就用 css 的 transform-origin 屬性吧。

// Tama.vue
.tama-root {
  // ...
  transform-origin: 90px 100%;
}
複製代碼

transform-origin 的單位能夠是像素,也能夠是百分號,而後咱們來指定 tama桑 的位置吧。

示例

添加動畫

終於... 作好了動畫的準備工做,先來個簡單的動畫【單擊向上跳 50px】,大概就只是【單擊以後改變 tama桑 y 軸的位置】。

單擊改變位置

加動畫既能夠在 Tama.vue 裏,也能夠在 App.vue 裏。這裏想讓 tama桑 自身有這個功能,因此在 Tama.vue 裏作比較天然,就直接在這裏寫吧。

// Tama.vue
<template>
  <img class="tama-root" src="/img/tama.svg" alt="タマさん" :style="{...}" @click="jump(50)" >
</template>
...
<script> export default { name: 'Tama', props: {...}, methods: { jump (height) { this.y -= 50 } } } </script>
複製代碼

@click 在單擊時調用 jump 方法,在裏面設定 y 爲 -50,而後這樣子作好了以後,單擊 tama 就會向上移動 50px。

蛋疼的是,這個時候瀏覽器的 console 會有這樣的警告。

示例

由於 y 是由父組件指定的,因此這裏不能去改它,(x, y) 也就是指定它的位置而已,因此要在內部添加相對位置。

首先,爲了要在內部添加變量,先把 data 寫了,而後添加 dx、dy 兩個變量。以後把 jump 方法裏的 y 變成 dy 。

// Tama.vue
<script> export default { name: 'Tama', props: {...}, data () { return { dx: 0, dy: 0 } }, methods: { jump (height) { this.dy -= height } } } </script>
複製代碼

template 這邊要指定座標的話就是 = 基礎位置(x, y) + 相對位置(dx, dy)。

// Tama.vue
<template>
  <img class="tama-root" src="/img/tama.svg" alt="タマさん" :style="{ transform: `translate(${x + dx}px, ${y + dy}px) ...` }" @click="jump(50)" >
</template>
複製代碼

給變動位置加上動畫

單擊的時候位置確實變了,可是尚未動畫。emm... 咱們去加上動畫吧。關於加動畫的方法呢,我考慮到有下面兩個點。

  1. 用連續的定時器控制 y 和 dy 座標。
  2. 一口氣設定好座標,在 css 裏作動畫。

第 1 點雖然可以控制很複雜的動畫,可是每次都要計算座標,就會變的很麻煩。第 2 點的話,只要指定變動後的值,瀏覽器會幫咱們加上動畫,並且還很流暢。

這裏固然選 2 啦。 代碼就只要 2 行就夠了。

// Tama.vue
.tama-root {
  // ...
  transition: transform 1s ease;
  will-change: transform;
}
複製代碼

will-change 是讓動畫變的更加順滑的「魔法」。(原本用「魔法」來理解我以爲不太好,詳細的我寫了另外一篇文章:用 will-change 實現 60fps 動畫

這樣子作好以後,單擊時 tama桑 就會滑上去了。簡單的動畫就這樣實現好了( •̀ ω •́ )y。

示例

指定動畫的時間和緩動效果

以前的例子咱們指定好了動畫的時間 (1s === 1秒),和緩動效果 (ease) 這樣子寫死的代碼。雖然這樣子已經可以指定動畫了,但咱們也仍是讓她可以受這個的控制吧。

data 里加上 duration 和 easing,template 里加上這兩個變量。

// Tama.vue
<template>
  <img ... :style="{ transform: ... , transition: `transform ${duration}ms ${easing}` }" @click="jump(50)" >
</template>
...
<script> export default { name: 'Tama', props: {...}, data () { return { ... duration: 1000, easing: 'ease' } }, ... } </script>
複製代碼

連續的動畫(一個動畫作完緊接着開始下一個動畫)

跳固然是從地面上跳起來,再落下去。接下來要考慮下連續的動畫。

大概就是這種感受...

// Tama.vue
jump (height) {
  this.dy = -height
  // 等待上個動畫結束
  this.dy = 0
}
複製代碼

用定時器實現連續的動畫

我想了一下,等待的方法有下面兩種:

  1. 單純的用定時器等待
  2. 監聽 transitionend 事件

雖然用 2 纔是穩的,可是多個動畫的話代碼結構就會至關蛋疼,此次仍是就用 1 吧。

// Tama.vue
jump (height) {
  this.dy = -height
  this.easing = 'ease-out'
  window.setTimeout(() => {
    this.dy = 0
    this.easing = 'ease-in'
  }, this.duration)
}
複製代碼

easing 也指定了以後,跳起來感受就會像雲同樣輕飄飄的。跳完也會回到原來的位置。

示例

用 async/await 實現連續動畫

以前,若是咱們想要作複雜的動畫,要是真的實現起來,那真的是... 一個字:吐血。

☠回調地獄(callback hell)☠警告。

若是要實現 三、4 個連續的動畫,會讓你知道什麼叫不忍直視。

爲了擺脫回調地獄,仍是用上 async/await 吧。話雖如此,其實也沒啥特別須要作的,只是把定時器 promise 化了而已。

// src/core/Time.js
export default {
  /** * Promise 等待指定時間 * @param {Number} ms 等待時間 * @return {Promise} 通過指定時間後 resolve */
  wait (ms) {
    return new Promise(resolve => {
      window.setTimeout(resolve, ms)
    })
  }
}
複製代碼

這裏沒有見過這樣寫的同窗,可能理解起來會有點困難,這裏的計時器 promise 化了以後,就能夠像這樣去控制等待時間。

console.log('這條消息顯示了')
await Time.wait(2000) // 這裏等待 2 秒
console.log('這條消息 2 秒後顯示了')
複製代碼

這篇文章不會講 Promise,也不會說明 async/await,記住用法的話,也就沒啥問題了吧。

用上了 Time.js 以後,剛纔那使人吐血的代碼就能夠寫成下面這樣子了:

// Tama.vue
<script> import Time from '@/core/Time' export default { ... async jump (height) { this.dy = -height this.easing = 'ease-out' await Time.wait(this.duration) this.dy = 0 this.easing = 'ease-in' await Time.wait(this.duration) } ... } 複製代碼

動畫的抽象與封裝

這樣子,3個以上的動畫也可以組合起來,連續的執行了。既然都到這一步了,乾脆把它封裝成像動畫庫同樣吧。

封裝成 Tween 風格

以前的例子,如下兩個步驟會頻繁的去操做動畫幀:

  1. 頻繁變動 data 裏面的變量
  2. Time.wait 等待動畫結束

下面就是抽出來的另外一種方法

Tama.vue
methods: {
  async tween (props, duration = 1000) {
    Object.assign(this.$data, props)
    this.$data.duration = duration
    await Time.wait(duration)
  },
  async jump (height) {
    await this.tween({ dy: -height, easing: 'ease-out' }, 1000)
    await this.tween({ dy: 0, easing: 'ease-in' }, 1000)
  }
}
複製代碼

剛追加的 tween 方法裏用了 Object.assign,用參數傳遞的方式,把 props 覆蓋掉 this.$data 的變量。以後再用 Time.wait 控制等待 duration 毫秒。 調用的時候要是用上這個方法,代碼就會簡約不少。

複雜動畫的封裝

封裝好了 tween 方法以後呢,多個動畫的組合就會變得很容易,用上這個讓我再提煉下 jump 的代碼吧。

// Tama.vue
async jump (height = 200, duration = 2500) {
  await this.tween({ dScaleY: 0.8, easing: 'ease' }, duration * 0.1)
  await this.tween({ dy: -height, dScaleY: 1.1, easing: 'ease-out' }, duration * 0.35)
  await this.tween({ dy: 0, dScaleY: 1.2, easing: 'ease-in' }, duration * 0.35)
  await this.tween({ dScaleY: 0.7, easing: 'ease' }, duration * 0.1)
  await this.tween({ dScaleY: 1.0, easing: 'ease' }, duration * 0.1)
}
複製代碼

跳躍前加上滯留的動做,跳躍的時候拉伸下身體,就算是隻有一張圖片,也能夠作到 pióng pióng 像布丁同樣很可愛的動做。😊

示例

到這裏,總結一下,把代碼所有都一口氣貼出來。 另外,除了 jump,順便把 walk 方法也給寫了一下。

// Tama.vue
<template>
  <img class="tama-root" src="/img/tama.svg" alt="タマさん" :style="{ transform: `translate(${x + dx}px, ${y + dy}px) scale(${scaleX * dScaleX}, ${scaleY * dScaleY}) rotate(${rotate + dRotate}deg)`, transition: `transform ${duration}ms ${easing}` }" @click="jump(200)" >
</template>

<style lang="scss" scoped> .tama-root { position: absolute; left: 0; top: 0; margin: -300px auto auto -90px; transform-origin: 90px 100%; will-change: transform; } </style>

<script> import Time from '@/core/Time' export default { name: 'Tama', props: { x: { type: Number, default: 200 }, y: { type: Number, default: 100 }, scaleX: { type: Number, default: 1.0 }, scaleY: { type: Number, default: 1.0 }, rotate: { type: Number, default: 0 } }, data () { return { dx: 0, dy: 0, dScaleX: 1.0, dScaleY: 1.0, dRotate: 0, duration: 1000, easing: 'ease' } }, methods: { async tween (props = {}, duration = 1000) { Object.assign(this.$data, props) this.$data.duration = duration await Time.wait(duration) }, async jump (height = 200, duration = 2500) { await this.tween({ dScaleY: 0.8, easing: 'ease' }, duration * 0.1) await this.tween({ dy: -height, dScaleY: 1.1, easing: 'ease-out' }, duration * 0.35) await this.tween({ dy: 0, dScale: 1.2, easing: 'ease-in' }, duration * 0.35) await this.tween({ dScaleY: 0.7, easing: 'ease' }, duration * 0.1) await this.tween({ dScaleY: 1.0, easing: 'ease' }, duration * 0.1) }, async walk (step = 100, duration = 500) { await this.to({ dRotate: 10, dScaleY: 0.8, easing: 'ease' }, duration * 0.2) await this.to({ dx: this.dx + step, dy: -step * 0.2, dRotate: -5, dScaleY: 1.1, easing: 'cubic-bezier(.04,.67,.52,1)' }, duration * 0.7) await this.to({ dy: 0, dRotate: 0, dScaleY: 1, easing: 'ease' }, duration * 0.1) } } } </script>
複製代碼

組合動畫

到這一步,就能夠調用抽象好的,好幾個動畫組合成的,複雜的,那個叫作 jump 和 walk 方法。最後,再用這些方法去組合更加複雜的動畫吧。

在 App.vue 加一個按鈕,點擊這個按鈕,就能夠給 tama桑 加上由 jump 和 walk 方法組合成的,連續的動畫。

// App.vue
<template>
  <div id="app">
    <button @click="play">Play</button>
    <tama ref="tama" :x="100" :y="300" :scaleX="0.5" :scaleY="0.5"></tama>
  </div>
</template>
複製代碼

按下按鈕的方法 play 大概就是這樣的感受:

// App.vue
async play () {
  const tama = this.$refs.tama
  await tama.jump(100, 1500)
  await tama.walk(100, 1200)
  await tama.walk(60, 600)
  await tama.walk(40, 400)
  await tama.jump(200, 2500)
}
複製代碼

小跳一下 -> 走 3 步 -> 最後來個大跳。這麼一連續的動做,就能夠這麼簡簡單單的就能表示出來。此次只有 tama桑 一我的,若是再來多幾個組合好動畫的人物的話,應該就能夠像遊戲同樣,組合成複雜的動做。

總結

  • css transform 的座標,在 Vue 裏用以參數的形式讓它聯動,就能夠很輕鬆的配置 svg 的位置、大小、角度哦!
  • 用上 css transition 了的話,就能夠給位置、大小、角度加上動畫哦!
  • 用上 async/await 了的話,使用起來感受就像 Tween 庫同樣,很複雜的動畫也能夠很輕鬆的去控制。
  1. 剛開始的時候決定好大小的話,用起來就很方便。
  2. Vue 的基本操做沒有介紹,不理解的時候,適當的去參照 Vue 入門文章之類的。
  3. 以前也寫過類文章,一個是用了 Vue 和 svg,作的射擊遊戲《<貓🐱魚🐟攻擊🌟>的代碼解說》,那裏面也用到了 tama桑,二是《用 Vue 和 firebase 的基本功能,製做流暢的我的網站代碼解說》,裏面的動畫跟這篇文章是同樣的。

譯者記

動畫看起來效果不錯,文章寫的很是的詳細,基本上只要會一點 Vue,就能夠輕鬆的駕馭了。不過 async/await 第一次見的話,那這裏也可以體驗一下感覺,同步的寫代碼也是很是爽的,不過都 9102 年了,還沒接觸 async/await 的話就有點... 這篇文章是從日語技術網站 qiita 上面搬運過來的,因此會有一些日文,已經獲得原做者的許可,以後的話看有機會繼續翻譯其它的吧。還有 tama桑 很可愛。

原地址 qiita.com/yuneco/item…

相關文章
相關標籤/搜索