フロントエンドマスターへの道 その4 Svelteでストライプナビゲーション


- [2020年のフロントエンドマスターになりたければこの9プロジェクトを作れ](https://qiita.com/rana_kualu/items/915345b8f3f870cfe2aa)

上記記事の4つめ。チュートリアルのURLは以下

- https://medium.com/codingthesmartway-com-blog/building-a-svelte-3-todo-app-from-start-to-deployment-1737f72c23a6

成果物は以下

![Peek 2020-01-03 00-11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17669/790d9115-c428-28e3-ed6a-9e0f94d9c441.gif)


- https://github.com/hbsnow-sandbox/svelte-stripe-navigation
- https://hbsnow-sandbox.github.io/svelte-stripe-navigation/

ご覧の通り、作ったのはToDoリストではありません

## Svelte

- https://svelte.dev/tutorial/

紹介されているURLよりも、まず最初に公式のチュートリアルが実際に手を動かしながら体験できて本当にわかりやすいので一通りやったほうがいいと思う、さらにここでやったあれってどうやるんだっけなって思ったときには [Example](https://svelte.dev/examples) で確認ができる

終えたところで気づくと思うんだけど、ToDoリストはすでにこの公式のチュートリアルにもあり題材としては微妙な気がするので、せっかくなのでSvelteを使って下記の記事

- [フロントエンドのコーディング課題６選-このフロントエンドの課題、実装できますか？](https://qiita.com/baby-degu/items/d68e52a0727248ba2750)

であげられている課題の6番目にあげられているストライプナビゲーションを作成することにします。チュートリアルは下記

- https://lokeshdhakar.com/dev-201-stripe.coms-main-navigation/

## インストール

- https://svelte.dev/blog/the-easiest-way-to-get-started

```terminal
npx degit sveltejs/template svelte-todo-example
```

degitを使うのが簡単。ただこのままだとTypeScriptではないし、とくにLintとかもないので他にいいテンプレートがあればそれを使ったほうがいいと思う。今回はほとんど使ったことのないrollupで消耗するのが嫌だったので、何もいじらずそのまま使ってます

## globalなCSS

App.svelteのstyleに記述した

```html
<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に触らないといけない。そういうときには以下のような記述をする必要がある

```html
  let dimension
  let elem
  onMount(() => {
    dimension = elem.getBoundingClientRect()
  })
```

## Animation

Svelteのアニメーションはかなり使いやすそうで、[transition](https://svelte.dev/docs#svelte_transition) と [animation](https://svelte.dev/docs#svelte_animate) のAPIが用意されている。ただこれらのAPIは、DOMのノードが削除されたり、入れ替わったりしたときのアニメーション用のAPIで、状態が変化したことによる特定要素のアニメーションについてどうこうするものではなさそうな気がする（たぶん）

なので、基本的にクラス名で、それができないのであればstyle属性に記述する必要がありそう。今回の場合はstyle属性で処理が必要になるところがいくつかあって

```html
  <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);
    "
  >
```

```html
  {#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>` こういうのは動きそうだなと思って試してみたが動かなかった。なので今回のように動的に長さをとって、状態によってその取得した長さを計算してとかやるのであれば

```html
transform:
  translateX({lastActiveMenu * 120}px)
  {calcBackgroundScale()}
;
```

こんな感じか

```
style="{fooStyle()}"
```

みたいな感じにしてしまったほうがよかったかも

## Store

writableだけでも良かったと思うんだけど、せっかくなのでreadableもつかった

```javascript
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が入る

作っていく過程でアニメーションのために最後にホバーしたアイテムがどれであったのかを取得する必要がでてきた

```javascript
let lastActiveMenu = null
$: if ($activeMenu !== null) {
  lastActiveMenu = $activeMenu
}
```

上記のように記述すると、$activeMenuがnullでないとき、lastActiveMenuの変更がリアクティブに反映される

## 感想

題材も悪かったのかもしれないが、Svelteの学習コストが低いというのはどういうところで学習コストが低いと判断されているのかはわからなかった。こう書いたら動くけど、この書き方がただしいのかは全然わからない、みたいな手探り状態だったのは情報量の少なさなのだろうか……VSCodeでのいい感じの環境も結局整えることはできなかった

公式チュートリアルはどのフレームワークよりもいい

途中まではLPみたいなアニメーションがそこそこあって、jQueryやVanillaで書かれるような小さいものであればその代替になるのではないかと思ったんだけど、正直ちょっと（自分のいまの理解度では）怖くて使えないかな