VRイベントサイト制作の振り返り2023 Nuxt3対応とWebアニメーションに寄せて

この記事はVR法人HIKKY Advent Calendar 2023、13日目の記事です。

https://qiita.com/advent-calendar/2023/hikky

昨日の記事は[@naninunenoy](https://qiita.com/naninunenoy)さんの[MonoBehaviourアンチが採用する技術](https://qiita.com/naninunenoy/items/d41bda893e848271d04d)でした。

## はじめに
株式会社HIKKYでデザインエンジニア[^1]をしている者です。主にイベントサイトのTOPページやLP等のコーディングをしています。業務ではNuxtとCSS(SCSS)を主に使っています。
今年からVketのイベントサイトをNuxt3で制作することになり、知見を得たのでシェアします。

## Nuxt3でコーディングした時のつらみ
### SVGが真っ黒になる問題
2022年まで、弊社ではNuxt2 Bridge + Composition API + SFCでWebサイトの開発をしていました。
2023年からのイベントサイト制作はNuxt3へ移行することにし、移行に伴い今まで使ってきたComponentsの移植を行いました。
その時、Nuxt2までは下記実装で読めていたSVGが、真っ黒になる問題が出てきました。
```vue
<template>
  <IconUser class="icon" />
</template>
<script setup lang="ts">
import IconUser from '@/assets/path/to/icon-user.svg'
setup() {
  return {
    IconUser
  }
}
</script>
<style lang="scss" scoped>
.icon {
  fill: #bcfff6;
}
</style>
```
Nuxt3で利用されているviteではSVGのfillの色や埋め込みstyleを読んでくれないことが分かりました。
解決策として、Nuxt3のプロジェクトではimport文の末尾を、当該SVGを利用する際色変えするかしないかで記述を分けることにしました。
#### 色変えする場合(アイコンなど)
```vue
<template>
  <div class="icon">
    <IconUser />
  </div>
</template>
<script setup lang="ts">
import IconUser from '@/assets/path/to/icon-user.svg?component'
</script>
<style lang="scss" scoped>
.icon {
  width: 48px;
  &:deep(path) {
    fill: #bcfff6;
  }
}
</style>
```
#### 色替えしない場合(飾りなど)
```vue
<template>
  <div class="icon">
    <IconUser />
  </div>
</template>
<script setup lang="ts">
import IconUser from '@/assets/path/to/icon-user.svg?url'
</script>
<style lang="scss" scoped>
.icon {
  width: 48px;
}
</style>
```

詳しくはviteのドキュメント、「静的アセットの取り扱い」をご参照ください。

https://ja.vitejs.dev/guide/assets.html

## イベントサイトやLP特有の問題
これ以降はNuxtから一旦離れますが、イベントサイトやLP特有と思われる問題について書きます。
### スクロール追従ボタンとスタッキングコンテキスト(Stacking Context)
人の目を惹きたいLPやTOPページは飾りが多くなりがちです。それ故、背景などで飾りとなる画像を`position: absolute;`無双しなければならない局面が出てきます。すると瞬く間にスタッキングコンテキストが増えて管理できなくなります。
これにより、追従ボタンがコンテンツの下をくぐってしまう現象が発生したことがありました。(iOS Chrome, iOS Safari)
どう対処したかというと、コンテンツをコンテナで囲って、そのコンテナのz-indexを追従ボタンより小さい値にすることでスタッキングコンテキストを抑え込むことにしました。
スタッキングコンテキストの解説は下記記事に譲ります。冒頭から例えがわかりやすかったです。

https://ics.media/entry/200609/

## イベントサイトを作る時に考えていたこと
### アニメーションの提案はイベントテーマの理解から
WEBデザイナーの方からリファレンスと共に「こういうアニメーションを入れたい」という提案を受けて実装することもありますが、デザインエンジニアの方からアニメーションを提案することもあります。
コーディングする際は、イベントテーマの理解と、デザインの意図の理解をまず最初に行います。
#### 例1. ComicVket 3(※Nuxt2製)

https://comic3.vket.com/

同人誌即売会ということで、セクションタイトルのルビに付箋を貼るイメージのアニメーションを付けました。
デジタル空間での温かみのある本との出会いがテーマだったため、イメージはアナログ的な付箋を貼る動きでした。電子書籍の付箋機能とイージングを同じにしています。動きは目視で合わせていました。
![comic3001.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13239/2b5e473f-7b30-5f39-1dfa-67619885ce16.gif)


#### 例2. Vket2023Summer, Vket2023Winter

https://event.vket.com/

https://event.vket.com/2023Summer/

テーマはCONNECT。つながりが大事なテーマということで、WEBデザイナーさんからも夏と冬でサイトを横に並べて見ると対になるように作りたいと要望があり、その点飾りの配置などで気を使って実装していました。ABOUTセクションで回っている円は、実は夏と冬で輪がつながっています。
![](https://storage.googleapis.com/zenn-user-upload/3a76e5e4b181-20231213.png)
2022年までのVketはふわっとした優しめのアニメーションを採用していましたが、今回のデザインの傾向がポップでビビッドだったため、アニメーションもどちらかというとdurationを短く、バキバキに動くアニメーションを多く取り入れました。例として、スケジュールが出てくる時のアコーディオン右側、矢印のついたボタンをご覧ください。
![](https://storage.googleapis.com/zenn-user-upload/b2da1c977d6d-20231213.gif)

### CSSアニメーションとWeb Animations APIの使い分け
次のような場合は、原則CSSアニメーション(`transition`や`@keyframes`)で賄っています。
- アニメーションを連続して発火させる必要がない
- アニメーションの再生は押下やスクロールといったユーザーの操作のみによる
- アニメーションの再生途中や再生後にscriptブロックでの値の操作、emitが必要ない

一方、Web Animations APIは、Nuxtで使うと次のような時に便利です。
- ある動きをした後に別の動きをする等、アニメーションを連続させたい
- イベント発火とともに親コンポーネントの状態も変化させたい(emitを使いたい)
- JavaScriptで実装してもらったアニメーションアイデアを、Nuxtの作法に合わせて組み込みたい

#### 例3. バーチャルマーケット2023リアルinアキバ

https://event.vket.com/2023Summer/real

このサイトのキービジュアルには仕掛けがあります。右上で動いている「侵略せよ！」を押すと……？

![2023summerreal001.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13239/3ed69aea-5b7d-132b-84e9-c6e3a52bea3d.gif)

このカットインは次のようなコードになっています。

```vuejs
<template>
  <button @click="invadeKV" />
  <div ref="overlay" />
</template>

<script setup lang="ts">
import { Ref } from 'vue'

// emitしたいイベントを定義
const emit = defineEmits<{
  (event: invade): void
}>

// 動かしたい要素をrefで取得
const overlay: Ref<HTMLElement | null> = ref(null)

// ここからWeb Animations API。順序のあるアニメーションはasync functionとして実装する
const invadeKV = async () => {
  // refで取得した要素が取れているかのチェック
  if(!overlay.value) return;
  overlay.value.style.transform = 'rotate(-45deg)'
  overlay.value.style.visibility = 'visible'
  await overlay.value.animate(
    // 使えるプロパティはCSSのkeyframesと同じ
    [{ transform: 'translateX(-100%)' }, { transform: 'translateX(0)' }],
    {
      duration: 500,
      easing: 'cubic-bezier(0.85, 0, 0.15, 1)',
      // アニメーションを重ねがけする設定。これは加算
      composite: 'add',
    }
  ).finished
  // finishedを受けて実行される次のアニメーション
  await overlay.value.animate(
    [{ transform: 'translateX(0)' }, { transform: 'translateX(100%)' }],
    {
      duration: 500,
      delay: 1000,
      easing: 'cubic-bezier(0.85, 0, 0.15, 1)',
      composite: 'add',
    }
  ).finished
  overlay.value.style.visibility = 'hidden'
  // すべてが終わったらイベントをemitすることも可能
  // 親コンポーネントを変化させたい時に使う
  emit('invade')
}
</script>
```


## 今後の展望
現在のところ、まだまだ「俺たちは雰囲気でアニメーションを実装している」という状態です。イージング関数やマイクロインタラクションを学んで、派手さを追い求めるだけでなく、手触りの良いイベントサイトを作りたいと考えています。

## 終わりに
HIKKYのUI・UXデザインチームは、WEBサイトに限らず、既存の枠に囚われない面白UI・UXを作り出す挑戦が出来る環境です。気になった皆様は弊社採用ページをチェックしてください。

https://www.hikky.co.jp/recruit

14日目の記事は[@otsuka_kenchan](https://qiita.com/otsuka_kenchan)さんの[Railsで開発を行うときに考えていること](https://qiita.com/otsuka_kenchan/items/27168d5f3964ccca12c5)です！お楽しみに！

[^1]: 弊社ではロジックに強いフロントエンドエンジニアをフロントエンドスペシャリストとし、CSSやデザインに関わる実装が得意な方のフロントエンドエンジニアをデザインエンジニアとしています。フロントエンドエンジニアと一口に言っても特性があると考えている筆者としては、得意なタスクを割り振っていただけるため、とても助かっています。