上記記事の4つめ。チュートリアルのURLは以下
成果物は以下
- https://github.com/hbsnow-sandbox/svelte-stripe-navigation
- https://hbsnow-sandbox.github.io/svelte-stripe-navigation/
ご覧の通り、作ったのはToDoリストではありません
Svelte
紹介されているURLよりも、まず最初に公式のチュートリアルが実際に手を動かしながら体験できて本当にわかりやすいので一通りやったほうがいいと思う、さらにここでやったあれってどうやるんだっけなって思ったときには Example で確認ができる
終えたところで気づくと思うんだけど、ToDoリストはすでにこの公式のチュートリアルにもあり題材としては微妙な気がするので、せっかくなのでSvelteを使って下記の記事
であげられている課題の6番目にあげられているストライプナビゲーションを作成することにします。チュートリアルは下記
インストール
npx degit sveltejs/template svelte-todo-example
degitを使うのが簡単。ただこのままだとTypeScriptではないし、とくにLintとかもないので他にいいテンプレートがあればそれを使ったほうがいいと思う。今回はほとんど使ったことのないrollupで消耗するのが嫌だったので、何もいじらずそのまま使ってます
globalなCSS
App.svelteのstyleに記述した
<style>
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600');
:root {
--padding: 16px 32px;
--transition-duration: 0.3s;
}
:global(*) {
box-sizing: border-box;
font-family: 'Open Sans', sans-serif;
}
:global(body) {
min-height: 100vh;
font-family: 'Open Sans', sans-serif;
background: linear-gradient(-45deg, #19C5FE, #4553FF);
padding: 20px;
}
</style>
場合によってはnormarize.cssみたいなものを普通にhtmlから読み込んでもいいと思う
要素の高さをとる
要素の高さをとるには、DOMに触らないといけない。そういうときには以下のような記述をする必要がある
let dimension
let elem
onMount(() => {
dimension = elem.getBoundingClientRect()
})
Animation
Svelteのアニメーションはかなり使いやすそうで、transition と animation のAPIが用意されている。ただこれらのAPIは、DOMのノードが削除されたり、入れ替わったりしたときのアニメーション用のAPIで、状態が変化したことによる特定要素のアニメーションについてどうこうするものではなさそうな気がする(たぶん)
なので、基本的にクラス名で、それができないのであればstyle属性に記述する必要がありそう。今回の場合はstyle属性で処理が必要になるところがいくつかあって
<div
class="content"
style="
{lastActiveMenu !== null ? `
width: ${parseInt(dimensions[$menuList[lastActiveMenu]].width)}px;
height: ${parseInt(dimensions[$menuList[lastActiveMenu]].height)}px;
` : ''
}
transform: translateX({lastActiveMenu * 120}px);
"
>
{#if dimensions.products}
<div
class="background"
style="
width: {parseInt(dimensions.products.width)}px;
height: {parseInt(dimensions.products.height)}px;
transform:
translateX({lastActiveMenu * 120}px)
{lastActiveMenu !== null ? `
scaleX(${dimensions[$menuList[lastActiveMenu]].width / dimensions.products.width})
scaleY(${dimensions[$menuList[lastActiveMenu]].height / dimensions.products.height})
` : ''
}
;
"
></div>
{/if}
こんな感じで書いたら動いた
とりあえずこう書いたら動いた、というだけなのでこれが最適な記述ではないかもしれない
<h1 style={{color: '#fff'}}>Hello World!</h1>
こういうのは動きそうだなと思って試してみたが動かなかった。なので今回のように動的に長さをとって、状態によってその取得した長さを計算してとかやるのであれば
transform:
translateX({lastActiveMenu * 120}px)
{calcBackgroundScale()}
;
こんな感じか
style="{fooStyle()}"
みたいな感じにしてしまったほうがよかったかも
Store
writableだけでも良かったと思うんだけど、せっかくなのでreadableもつかった
import { writable, readable} from 'svelte/store'
export const menuList = readable([
'products',
'developers',
'company'
])
export const activeMenu = writable(null)
このreadableは試しに使っただけで、こういった不変のメニューリストとかであれば main.js のpropsとかに入れるもののような気がする
最後にホバーしたアイテムを取得する
現在のホバーされている状態はstoreのactiveMenuに保存したが、これにはホバーしていないときにはnull、何かにホバーしてるときにはnumberが入る
作っていく過程でアニメーションのために最後にホバーしたアイテムがどれであったのかを取得する必要がでてきた
let lastActiveMenu = null
$: if ($activeMenu !== null) {
lastActiveMenu = $activeMenu
}
上記のように記述すると、$activeMenuがnullでないとき、lastActiveMenuの変更がリアクティブに反映される
感想
題材も悪かったのかもしれないが、Svelteの学習コストが低いというのはどういうところで学習コストが低いと判断されているのかはわからなかった。こう書いたら動くけど、この書き方がただしいのかは全然わからない、みたいな手探り状態だったのは情報量の少なさなのだろうか……VSCodeでのいい感じの環境も結局整えることはできなかった
公式チュートリアルはどのフレームワークよりもいい
途中まではLPみたいなアニメーションがそこそこあって、jQueryやVanillaで書かれるような小さいものであればその代替になるのではないかと思ったんだけど、正直ちょっと(自分のいまの理解度では)怖くて使えないかな