你們好,這裏是 UX(交互體驗設計師)& 前端開發,而且還喜歡畫畫的 yuki!@yuneco。 在這篇文章裏,爲了可以更加輕鬆地去使用 Vue 和 css animation
,基礎的部分,將會跟你們一步步的詳細解釋。目標以下圖↓所示,用 JavaScript
自由的控制動畫。javascript
源碼送上 github.com/yuneco/css-…css
那麼先從最簡單的 svg 標籤開始。用 Vue 自由地配置、使其變形。而後利用 css
的 transition
來作動畫,最後把動畫抽象封裝,運用到更加複雜的場景上。html
anime.js
或者 pixi.js
。javascript
、css 動畫
。雖然還有不少理由,但能點亮本身的【本身組建可以理解的動畫】這方面的技能樹,無疑也是很高興的。文章稍稍有點長,若是你能看完的話,那我也很高興。前端
第一步,顯示這篇教程要使用到的 svg,用 Illustrator 製做本身喜歡的角色去,依次從菜單上選擇 [ファイル] -> [書き出し] -> [スクリーン用に書き出し],格式選擇 svg,從右邊齒輪同樣的圖標,顯示設定。vue
設定看起來有點複雜的😓,這裏在 Vue 也沒有那麼麻煩的去使 用 svg,因此這裏的設定不須要太在乎,右下角的 Responsive(レスポンシブ)
選擇記得要取消掉。java
設定好了以後,「設定を保存」->「アートボードを書き出し」導出 svg 文件,沒有 Illustrator
的同窗用其它的文件也 ok。怕麻煩的同窗,我姑且在 github 上也放了一份...node
用瀏覽器打開,大概就是這種感受,名字叫 tama桑
,如今剛決定的。爲了方便理解,我特地加了 1 像素的邊框。git
無論怎麼說,不建立的 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 文件 放在 /public/img/
, img
目錄沒有的話就本身建一下。接下來就先用 Vue 把 svg 顯示出來吧。
只是普通的顯示 svg 的話跟 Vue 沒有關係。另外爲了以後的考慮,先把 src/components/Tama.vue
文件建立好,代碼像下面這樣。
<template>
<img src="/img/tama.svg" alt="タマさん">
</template>
複製代碼
這篇教程用最簡單的方法,用 img
標籤來讀取 svg。固然用其它的方法也行,好比像下面這樣:
固然還有各類各樣的方法,特別是方案 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
指定座標,雖然指定位置也能夠用 top
跟 left
,可是用 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 能夠指定橫向仍是縱向,因此這裏加上 scaleX
和 scaleY
兩種屬性。
// 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 點雖然可以控制很複雜的動畫,可是每次都要計算座標,就會變的很麻煩。第 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
}
複製代碼
我想了一下,等待的方法有下面兩種:
雖然用 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 也指定了以後,跳起來感受就會像雲同樣輕飄飄的。跳完也會回到原來的位置。
以前,若是咱們想要作複雜的動畫,要是真的實現起來,那真的是... 一個字:吐血。
☠回調地獄(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個以上的動畫也可以組合起來,連續的執行了。既然都到這一步了,乾脆把它封裝成像動畫庫同樣吧。
以前的例子,如下兩個步驟會頻繁的去操做動畫幀:
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桑
一我的,若是再來多幾個組合好動畫的人物的話,應該就能夠像遊戲同樣,組合成複雜的動做。
tama桑
,二是《用 Vue 和 firebase 的基本功能,製做流暢的我的網站代碼解說》,裏面的動畫跟這篇文章是同樣的。動畫看起來效果不錯,文章寫的很是的詳細,基本上只要會一點 Vue,就能夠輕鬆的駕馭了。不過 async/await 第一次見的話,那這裏也可以體驗一下感覺,同步的寫代碼也是很是爽的,不過都 9102 年了,還沒接觸 async/await 的話就有點... 這篇文章是從日語技術網站 qiita 上面搬運過來的,因此會有一些日文,已經獲得原做者的許可,以後的話看有機會繼續翻譯其它的吧。還有 tama桑
很可愛。