【HTMLアプリ作成】SvelteでSVG要素を直接操作して制限時間表示できるタイマーを作ってみる
※ 当ページには【広告/PR】を含む場合があります。
2021/01/20
Svelteアプリのデモから学ぶ
SVGにおけるPathを使った直線(L)と円弧(A)の基本
<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)
...(MとかLとかのコマンドが存在)
A [x軸方向の半径] [y軸方向の半径] [x軸からの傾き(角度)]
[円弧選択フラグ1(large-arc-flag)] [円弧選択フラグ2(sweep-flag)]
[終点のx座標] [終点のy座標]
円弧選択フラグ
<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>
円弧選択フラグ1(large-arc-flag)
円弧選択フラグ2(sweep-flag)
残り時間をビジュアル化する
<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要素をリアクティブに操作する
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>
...
M 0 0
L 0 -50
(arcEndPosX, arcEndPosY)
タイマーカウンターを利用して表示内容を変える
//...中略
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);
//...以下略
クロックの見栄えを改善する
<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コードの解説
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>
...
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);
まとめ
記事を書いた人
ナンデモ系エンジニア
これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。
カテゴリー