tweenedtweened
tweened 實作動態漸變色票 我們先前花了三天的時間好好介紹了 Svelte 的 Store,為了要能夠讓資料有水平溝通的管道,或者為了讓一般 Javascript 的變數,能夠跟 Svelte 元件當中的變數有溝通的橋樑,Svelte 提供了 Store 這個方便的工具。我們不僅可以使用 Svelte 打造的最基本的 writable Store,同時還可以從 writable Store 出發,快速的客製化我們需要的 Store。
以為 Svelte 只提供這麼樸素的 Store 嗎?在看過昨天第 27 天:Svelte 的過場:transition的文章之後,應該有發現到 Svelte 其實是個很花俏的傢伙了吧 😂 所以說,除了最基本的 writable Store 之外,Svelte 還另外提供了一個有趣的東西:具有動態效果的 Store。今天要介紹的,就是這個有趣的動態效果 Store:tweened。
tweened首先我們可以到 Svelte 的 REPL (Read-Eval-Print-Loop) 寫一段程式碼:
<script>
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
const progress = writable(0);
onMount(() => {
const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
return () => clearInterval(interval);
})
</script>
<progress value={$progress} />
第四行:const progress = writable(0);
初始化一個 writable Store,命名為 progress。
第七行:const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
將 setInterval 放進 onMount 當中,並利用 setInterval 的功能,讓 progress 一秒一秒從 0 跳到 1,再從 1 跳回 0。
第十二行:<progress value={$progress} />
並讓 progress 當中儲存變數的變化反應到 HTML 元素 <progress> 上。

圖一、我跳到 1 了!我跳回 0 了!我又跳到 1 了!我又跳回 0 了!
可以這樣跳來跳去固然有趣,但是只有非 0 即 1 的 <progress> 似乎稍嫌呆板。讓我們試試看具有動態效果的 Store tweened 看看吧:
<script>
import { onMount } from 'svelte';
import { tweened } from 'svelte/motion';
const progress = tweened(0);
onMount(() => {
const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
return () => clearInterval(interval);
})
</script>
<progress value={$progress} />
第三行:import { tweened } from 'svelte/motion';
引入 tweened。
第四行:const progress = tweened(0);
因為 tweened 其實也是一種 Store,使用方法就像 Store。首先初始化一個 tweened,並將其命名為 progress。
第七行:const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
因為 tweened 也是一種 Store,所以也可以用 $progress 來取得 progress 當中儲存的變數的值,並且用 set 這個方法來改變儲存的變數。
第十二行:<progress value={$progress} />
在 HTML 當中也可以展開 Javascript,並且用 $progress 拿到變數的值。

圖二、看我伸縮自在的 tweened!
是不是可愛多了呢?tweened 可以在前值根後值之間做出補間的效果,藉此讓使用者互動介面的表現能力更強。既然 tweened 有補間的效果,想必我們也可以使用參數來改變補間的變化方式囉。沒錯,tweened 提供四個參數讓我們可以對補間效果做更自由的調整,這幾個參數包括:
delay:補間效果開始的時間,單位是毫秒。duration:補間效果持續的時間,單位同樣是毫秒。easing:補間效果的變化方式,就跟昨天第 27 天:Svelte 的過場:transition介紹過的 easing 是相同的意思。interpolate:補間的定義方式。除了數字之外,藉由 interpolate,只要定義得當,我們也可以對文字、陣列、或是色碼做出補間的效果。 看到了嗎?色碼也可以用 tweened 來做出補間的效果,是不是很棒呢。今天就讓我們用新學的動態效果 tweened 來替我們的色票產生器做出更出色的變化吧!
tweened 實作動態漸變色票我們的色票產生器可以隨機產生不同色碼的色票,今天想要實作的效果,就是在色碼重新進行隨機生成的時候,加入色碼補間的效果,讓色票是逐漸地從原本的顏色變成新的顏色。用文字說明太抽象的話,先來實際來看個成果吧:

圖三、令人著迷的漸變色票🎉
在開始動手前,讓我們先把 Palettes.svelte 重新整理一下:
/src/lib/Palettes.svelte
<script>
import Palette from "./Palette.svelte";
export let palettes;
</script>
<div class="palettes">
{#each palettes as { hex, locked, id } (id)}
<Palette bind:hex bind:locked />
{:else}
<div class="card no-color">
<div>
<p>No color to show. Please add a color.</p>
</div>
</div>
{/each}
</div>
第二行:import Palette from "./Palette.svelte";
我們想要把 each 段落當中迭代的一張一張色票提取出來,變成新的 Svelte 元件 Palette.svelte。所以這邊先說我們要引入 Palette 這個 Svelte 元件,等等我們在來詳細把 Palette 實作出來。
第八行:<Palette bind:hex bind:locked />
引入 Palette 之後,就可以在 each 邏輯區塊當中簡簡單單的寫這麼一行,其他詳細的內容就可以放在 Palette 這個 Svelte 元件裡頭。記得要把 hex 跟 locked 都用 bind 綁定起來。
接著來實作 Palette.svelte:
/src/lib/Palette.svelte
<script>
import unlock from "../assets/unlock.svg";
import lock from "../assets/lock.svg";
import { toastStore } from "./toastStore";
export let hex;
export let locked;
const getHexName = async (hex) => {
const url = `https://www.thecolorapi.com/id?hex=${hex}`;
const res = await fetch(url);
const json = await res.json();
return json.name.value;
};
const handleLock = ({ locked, hex }) =>
toastStore.addToast({ action: locked ? "lock" : "unlock", hex });
</script>
<div class="card">
<div class="palette" style="background: #{hex}" />
<div class="hex-code">
<input type="text" size="10" bind:value={hex} />
<p>
{#await getHexName(hex)}
wait...
{:then name}
{name}
{/await}
</p>
</div>
<!-- svelte-ignore a11y-no-static-element-interactions a11y-click-events-have-key-events -->
<div class="lock-icon">
<label>
<input
type="checkbox"
bind:checked={locked}
on:change={() => handleLock({ locked, hex })}
/>
{#if locked}
<img src={lock} alt="color-locked" />
{:else}
<img src={unlock} alt="color-unlocked" />
{/if}
</label>
</div>
</div>
第五行:export let hex;
記得要將 hex 用 export 這個關鍵字使其變成 Palette 這個 Svelte 元件的 Property。
第六行:export let locked;
記得要將 locked 用 export 這個關鍵字使其變成 Palette 這個 Svelte 元件的 Property。
剩下的程式碼沒有太大改變就不贅述了。如此一來就完成將 Palettes.svelte 當中代表一張一張色票的程式碼獨立出來的動作了。接下來我們就來客製化一下 tweened,做出可以對色碼進行補間的 tweened。首先建立一個新的檔案 hexStore.js:
/src/lib/hexStore.js
import { tweened } from "svelte/motion";
export const createHexStore = (hex) => {
const hexStore = tweened(hex, {
duration: 500,
interpolate: (a, b) => (t) => {
const rA = parseInt(a.slice(0, 2), 16);
const gA = parseInt(a.slice(2, 4), 16);
const bA = parseInt(a.slice(4, 6), 16);
const rB = parseInt(b.slice(0, 2), 16);
const gB = parseInt(b.slice(2, 4), 16);
const bB = parseInt(b.slice(4, 6), 16);
const rT = ("0" + Math.round((rB - rA) * t + rA).toString(16)).slice(-2);
const gT = ("0" + Math.round((gB - gA) * t + gA).toString(16)).slice(-2);
const bT = ("0" + Math.round((bB - bA) * t + bA).toString(16)).slice(-2);
return rT + gT + bT;
},
});
return hexStore;
}
第一行:import { tweened } from "svelte/motion";
引入 tweened。
第三行:export const createHexStore = (hex) => {
宣告一個函式 createHexStore。這個函式會吃進一個參數 hex,並回傳可以對色碼進行補間的 tweened。
第四行:const hexStore = tweened(hex, {
初始化一個 tweened。
第五行:duration: 500,
設定 tweened 補間的變化全程時間為 500 毫秒。
第六行:interpolate: (a, b) => (t) => {
這就是客製化 tweened 讓 tweened 能夠對色碼進行補間的重點。interpolate 是一個函式,接受代表起始值 a 跟結束值 b 的參數,然後回傳一個補間函式,也就是時間 t 的時候應該呈現的補間的值。舉例來說,如果起始值是 1,結束值是 0,補間為線性變化,那麼時間進行了 0.5 時,補間函式應該要能算出補間的值為 0.5。用色碼來做解釋的話,如果起始色碼是 rgb(0, 0, 0),結束色碼是 rgb(255, 255, 255),同樣讓補間線性變化,那麼時間是 0.5 時,補間函式應該要能算出補間色碼為 rgb(127.5, 127.5, 127.5)。不過色碼不允許小數點,所以還要做個 Math.round() 四捨五入一下。
這樣一來就完成我們客製化的 tweened 了。回頭看看前面 tweened 應用的例子,其實我們想要做的動作就是用 tweened 儲存色碼的資料,並顯示在 <div class="palette"> 這個 HTML 元素上。現在已經有了可以對色碼做出補間的 tweened 了,讓我們把這個厲害的 tweened 放進代表一張色碼的 Palette.svelte 當中:
/src/lib/Palette.svelte
<script>
/* 省略無關的程式碼 */
import { createHexStore } from "./hexStore";
const hexStore = createHexStore(hex);
$: hexStore.set(hex);
</script>
<div class="card">
<div class="palette" style="background: #{$hexStore}" />
<div class="hex-code">
<!—省略無關的程式碼 -->
</div>
<div class="lock-icon">
<!—省略無關的程式碼 -->
</div>
</div>
第三行:import { createHexStore } from "./hexStore";
引入 createHexStore。
第五行:const hexStore = createHexStore(hex);
我們用 createHexStore(hex) 初始化一個客製化的 tweened。
第六行:$: hexStore.set(hex);
用第 09 天:Svelte 中的 Javascript:陳述介紹過的互動陳述 $:,如果 hex 發生變化,那就一起更新我們的 hexStore。記得 hexStore 本質上就是 tweened,就是一種 Store,所以可以用 hexStore.set 來改變儲存在裡面的變數。
第十行:<div class="palette" style="background: #{$hexStore}" />
並且在 <div class="palette"> 當中將 background 設定成 $hexStore,利用自動訂閱 (auto-subscription) 的方式,直接取得 hexStore 當中的值。

圖四、再依次看著令人著迷的漸變色票🎉
今天關於 Svelte 動態效果 tweened 的介紹就到這邊了。完整的程式碼可以在 Github 資料庫當中找到,那麼謝謝大家的閱讀!