【HTMLアプリ作成】SvelteでSVG要素を直接操作して制限時間表示できるタイマーを作ってみる
※ 当ページには【広告/PR】を含む場合があります。
2021/01/20

JSフレームワークの中でもビルド後の生成ファイルのサイズが最軽量級で、コードもスッキリしていて、初学者に今もっともオススメの
最近、別のブログの方で、Svelteアプリの開発環境をサクッと構築するやり方を解説してみました。
Svelteを始めてみたい方はそちらの記事をじっくり読んで頂くとして、今回はSvelteアプリ作成に慣れるために、HTML&CSSゲームなどに応用のできるタイマーっぽいものを一から作成する過程を解説してみます。
Svelteアプリのデモから学ぶ
Svelteフレームワークの扱いに慣れていないうちは、
今回は、
SVGにおけるPathを使った直線(L)と円弧(A)の基本
なお、SVGのビューポートの概念や座標定義に関して復習したい場合には、以前特集した以下の記事を参照してください。
今回の例でいうと、Pathによる
例えばPathのLコマンドで簡単な直線を連続的に描こうとすると、pathタグのd属性に描画コマンドを記述していくことになります。
<svg width="1000" height="1000">
<path d="
M 10 0
L 10 200
L 100 200
L 50 50
L 30 250
L 120 40"
stroke="black"
stroke-width="10"
fill="none"/>
</svg>
とこのように書くと、最初の
点(10, 0)
(10, 200) > (100, 200) > (50, 50) > (30, 250) > (120, 40)

直線を描くLコマンドは終点座標を指定するだけですので利用はとても簡単でしたが、円弧コマンド(A)は少し厄介です。
...(MとかLとかのコマンドが存在)
A [x軸方向の半径] [y軸方向の半径] [x軸からの傾き(角度)]
[円弧選択フラグ1(large-arc-flag)] [円弧選択フラグ2(sweep-flag)]
[終点のx座標] [終点のy座標]
見てのように、Aコマンドの引数は7つもあるので所見で混乱しやすいです。
最初に使うと、
Aコマンドは現在留まっている点から、次の終点までを結ぶ、楕円弧を計算して軌跡を描画してくれる機能です。
ですので、2点間の円弧というのは最大4通り描くことが可能ですので、どの円弧を選択するのかを伝えるのが
円弧選択フラグ
<svg width="1000" height="1000" viewBox="100 100 1000 1000">
<path d="
M 200 200
L 300 300"
stroke="black" stroke-width="10" fill="none"/>
<path d="
M 200 200
A 80 50 40
1 0
300 300"
stroke="blue" stroke-width="10" fill="none"/>
<path d="
M 200 200
A 80 50 40
0 0
300 300"
stroke="red" stroke-width="10" fill="none"/>
<path d="
M 200 200
A 80 50 40
0 1
300 300"
stroke="green" stroke-width="10" fill="none"/>
<path d="
M 200 200
A 80 50 40
1 1
300 300"
stroke="yellow" stroke-width="10" fill="none"/>
</svg>
Aコマンドを使うときには、この2つのフラグの組み合わせ(4通り)で、どの円弧を描きたいのかを明示に指定する必要があります。

一般に、
円弧選択フラグ1(large-arc-flag)
次に、
円弧選択フラグ2(sweep-flag)
このフラグ値を1にすると時計回りの円弧(この場合緑か黄色の円弧)を選択でき、フラグ値0で反時計周りの円弧(赤および青の円弧)を指定することになります。
残り時間をビジュアル化する
ということでSVGの直線・円弧の描き方のポイントを押さえて貰ったところで、以下のような制限時間表示するクロックを作ってみます。
このアプリは以下のソースコードをビルドしたものになります。
<script lang="ts">
import { onMount } from 'svelte';
const period = 10; // [秒]
const ticks = 300;
const timerPeriod = period * 1000 / ticks;
let timerCount = 0;
//👇SVGの円弧を終点座標をリアクティブに更新する
$: arcEndPosX = 50 * Math.sin(2*Math.PI*(timerCount / ticks));
$: arcEndPosY = -50 * Math.cos(2*Math.PI*(timerCount / ticks));
$: arcFlag = timerCount >= (ticks / 2) ? 1 : 0;
let messageSpan: any;
onMount(() => {
const interval = setInterval(() => {
timerCount = timerCount >= ticks - 1 ? 0 : timerCount + 1;
if (timerCount < ticks * 0.5) {
messageSpan.textContent = `所要時間は${period}秒です`;
} else if (timerCount >= ticks * 0.5 && timerCount < ticks ) {
const remainTime = period * (1 - timerCount / ticks);
messageSpan.textContent = `残り ${Math.floor(remainTime + 1)} 秒!`;
}
}, timerPeriod);
return () => {
clearInterval(interval);
};
});
</script>
<svg width="100" height="100" viewBox='-100 -100 200 200'>
<path class="arc1" d="
M 0 0
L 0 -50
A 50 50 0
{arcFlag} 1
{arcEndPosX} {arcEndPosY}
Z"/>
</svg>
<p>{Math.floor(period * timerCount / ticks)}秒</p>
<span bind:this="{messageSpan}"></span>
<style lang="scss">
svg {
width: 300px;
height: auto;
path.arc1 {
fill: rgb(145, 145, 145);
}
}
</style>
ではこのコードの実装のポイントを簡単に解説していきましょう。
SvelteでSVG要素をリアクティブに操作する
他のJSフレームワークでも出来なくは無いのですが、Svelteはより直観的に動的な変化を伴うSVGの記述が出来ます。
const period = 10; // [秒]
const ticks = 300;
const timerPeriod = period * 1000 / ticks;
let timerCount = 0;
//👇SVGの円弧を終点座標(と円弧の選択フラグ)をリアクティブに更新する
$: arcEndPosX = 50 * Math.sin(2*Math.PI*(timerCount / ticks));
$: arcEndPosY = -50 * Math.cos(2*Math.PI*(timerCount / ticks));
$: arcFlag = timerCount >= (ticks / 2) ? 1 : 0;
onMount(() => {
const interval = setInterval(() => {
//👇300回(ticks)カウントしたらカウンターをゼロにリセット
timerCount = timerCount >= ticks - 1 ? 0 : timerCount + 1;
//...中略
//👇300カウントが10秒になるようにインターバル時間(timerPeriod)を調整
}, timerPeriod);
//...以下略
こうしておくことで、
timerCount
arcEndPosX / arcEndPosY / arcFlag
...
<svg width="100" height="100" viewBox='-100 -100 200 200'>
<path class="arc1" d="
M 0 0
L 0 -50
A 50 50 0
{arcFlag} 1
{arcEndPosX} {arcEndPosY}
Z"/>
</svg>
...
SVG画像自体は閉じたPath(最後のZを付ける)で描いています。
M 0 0
L 0 -50
(arcEndPosX, arcEndPosY)
前述したように、SVGの円弧は指定した2点から円弧を作る関係で潜在的に4通りの円弧が描けるため、円弧選択フラグの1・2で適切に選ばないといけません。
今回の場合には楕円ではないので、フラグ1か2のどちらか1つを切り替えるだけできちんとした円弧の切り替わりを作ることが出来ます。
タイマーカウンターを利用して表示内容を変える
所要時間を表したクロックがくるくるまわるだけだとつまらないので、残り時間に連動して表示内容も変わるようにしてみます。
//...中略
onMount(() => {
const interval = setInterval(() => {
//👇300回(ticks)カウントしたらカウンターをゼロにリセット
timerCount = timerCount >= ticks - 1 ? 0 : timerCount + 1;
if (timerCount < ticks * 0.5) { //👈クロックが半分以下(~150カウント)の場合
messageSpan.textContent = `所要時間は${period}秒です`;
} else if (timerCount >= ticks * 0.5 && timerCount < ticks ) {
//👇クロックが半分以上(>150)になった時の処理
const remainTime = period * (1 - timerCount / ticks);
messageSpan.textContent = `残り ${Math.floor(remainTime + 1)} 秒!`;
}
//👇300カウントが10秒になるようにインターバル時間(timerPeriod)を調整
}, timerPeriod);
//...以下略
これで残り時間が半分を切ったときに表示を切り替えることが出来ます。
クロックの見栄えを改善する
さらにモノクロだと少し味気ないので、残り時間と連動するように背景色を変えるように改造してみます。
まず改造したアプリは以下のようになります。
ちょっと改造を施したSvelteコードは以下のようになります。
<script lang="ts">
import { onMount } from 'svelte';
const period = 10;
const ticks = 300;
const timerPeriod = period * 1000 / ticks;
let timerCount = 0;
$: arcEndPosX = 50 * Math.sin(2*Math.PI*(timerCount / ticks));
$: arcEndPosY = -50 * Math.cos(2*Math.PI*(timerCount / ticks));
$: arcFlag = timerCount >= (ticks / 2) ? 1 : 0;
//👇SVGを色遷移するためリアクティブ変数と関数を追加
$: colorFill = rgb(0, 255, 255);
function rgb(_r: number, _g: number, _b: number) {
let code = (1 << 24) + (_r << 16) + (_g << 8) + _b;
return `#${code.toString(16).replace(/^1/,'')}`;
}
let messageSpan: any;
onMount(() => {
const interval = setInterval(() => {
timerCount = timerCount >= ticks - 1 ? 0 : timerCount + 1;
if (timerCount < ticks * 0.5) {
colorFill = rgb(0, 255, 255);
messageSpan.textContent = `所要時間は${period}秒です`;
} else if (timerCount >= ticks * 0.5 && timerCount < ticks*0.75 ) {
const remainTime = period * (1 - timerCount / ticks);
messageSpan.textContent = `残り ${Math.floor(remainTime + 1)} 秒!`;
const colorShit = Math.round(255 * (remainTime/ (period / 4) - 1) );
colorFill = rgb(255 - colorShit, 255, colorShit);
} else if (timerCount >= ticks * 0.75 && timerCount < ticks) {
const remainTime = period * (1 - timerCount / ticks);
messageSpan.textContent = `残り ${Math.floor(remainTime + 1)} 秒!`;
const colorShit = Math.round(255 * remainTime / (period / 4));
colorFill = rgb(255, colorShit, 0);
}
}, timerPeriod);
return () => {
clearInterval(interval);
};
});
</script>
<svg width="100" height="100" viewBox='-100 -100 200 200'>
<path class="arc1" d="
M 0 0
L 0 -50
A 50 50
0 {arcFlag} 1
{arcEndPosX} {arcEndPosY}
Z" fill="{colorFill}"/>
</svg>
<p>{Math.floor(period * timerCount / ticks)}秒</p>
<span bind:this="{messageSpan}"></span>
<style lang="scss">
svg {
width: 300px;
height: auto;
}
</style>
改造したSvelteコードの解説
まず、この改造では、SVG画像のPathに直接
fill="{色}"
...
<svg width="100" height="100" viewBox='-100 -100 200 200'>
<path class="arc1" d="
M 0 0
L 0 -50
A 50 50
0 {arcFlag} 1
{arcEndPosX} {arcEndPosY}
Z" fill="{colorFill}"/> //👈ここで色を注入
</svg>
...
ここで定義したRGB色コード文字列を与える以下の関数は、SvelteだけでなくJavascript全般に使えます。
function rgb(_r: number, _g: number, _b: number) {
let code = (1 << 24) + (_r << 16) + (_g << 8) + _b;
return `#${code.toString(16).replace(/^1/,'')}`;
}
//使用例
console.log(rgb(124, 42, 55)); //👉#7c2a37
console.log(rgb(0xff, 0xc3, 0x20)); //👉#ffc320
使い方としては、
0 ~ 255
むしろここから色をシフトさせていくの方が難しく、今回は残り時間に連動させて
水色 > 黄色 > 赤色
const interval = setInterval(() => {
timerCount = timerCount >= ticks - 1 ? 0 : timerCount + 1;
if (timerCount < ticks * 0.5) {
//👇経過時間が半分以下なら色を固定
colorFill = rgb(0, 255, 255);
messageSpan.textContent = `所要時間は${period}秒です`;
} else if (timerCount >= ticks * 0.5 && timerCount < ticks*0.75 ) {
//👇経過時間が半分以上75%未満なら水色から黄色に色コードシフト
const remainTime = period * (1 - timerCount / ticks);
messageSpan.textContent = `残り ${Math.floor(remainTime + 1)} 秒!`;
const colorShit = Math.round(255 * (remainTime/ (period / 4) - 1) );
colorFill = rgb(255 - colorShit, 255, colorShit);
} else if (timerCount >= ticks * 0.75 && timerCount < ticks) {
//👇経過時間が75%以上なら黄色から赤色に色コードシフト
const remainTime = period * (1 - timerCount / ticks);
messageSpan.textContent = `残り ${Math.floor(remainTime + 1)} 秒!`;
const colorShit = Math.round(255 * remainTime / (period / 4));
colorFill = rgb(255, colorShit, 0);
}
}, timerPeriod);
ここら辺はプログラマーの遊び心で色々と応用できると思いますので、もっと面白い効果などでも同じようなテクニックで作り込んでいけると思います。
まとめ
今回は、SvelteでSVG画像を柔軟に扱うための話を具体例を示しながら解説していきました。
Svelteの公式ページに色々とサンプルが紹介されいます通り、もっと派手なアニメーション効果も直感として分かりやすく実装できるのが、Svelteアプリ開発の魅力です。
是非とも自分の指でキーボードをカタカタ叩いてみて、Svelteコードで出来ることを実感してみてはいかがでしょうか。