---
title: カチカチするたびランダムに回転する僕の作り方 with Astro
tags:  #cybozu #css #javascript #react #astro  
author: [Futa HIRAKOBA](https://convert.docswell.com/user/korosuke613)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/PEXDPZP17X.jpg?width=480
description: 2023年06月30日（金）に Cybozu Frontend Day という社内イベントで発表した資料です。  アイコンが動くバージョン: https://rotate-my-icon.vercel.app english version: https://www.docswell.com/s/korosuke613/58GMW2-2023-06-30-rotate-my-icon-en  # 内容  https://korosuke613.dev/ のトップページの僕の画像をクリックすると、ランダム(X軸、Y軸、Z軸、X軸 &amp; Y軸、X軸 &amp; Z軸、Y軸 &amp; Z軸)に回転します。 それを React を使った Astro (MPA) で行う方法を伝授します。  僕はフロントエンドよわよわエンジニアなので、発表時は用語等が間違ってる恐れがあります。ご了承ください。  [keywords]  CSS, JavaScript, React, Astro, Browser
published: June 30, 23
canonical: https://convert.docswell.com/s/korosuke613/ZLLQJQ-2023-06-30-rotate-my-icon
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/PEXDPZP17X.jpg)

カチカチするたびランダムに
回転する僕の作り方 with Astro
金
2023/06/30 ( )
Cybozu Frontend Day
平木場 風太 &lt;Futa Hirakoba&gt;


# Page. 2

![Page Image](https://bcdn.docswell.com/page/3EKXD8DM7D.jpg)

平木場 風太 - Futa Hirakoba
🌋 出身 - 鹿児島
🏢 勤め先 - サイボウズ株式会社 / 開発本部 / 生産性向上チーム
🧑‍💻 役割 - Engineering Productivity
🍣 好きな食べ物 - チキン南蛮、辛麺
💪 AWS や CI/CD ツール、Terraform などを触ることが多い
💀 フロントエンドはなんにもわからない
[1]
@korosuke613
@shitimi_613
1.
写真は桝元の辛麺（トマト 5 辛中華麺チーズトッピング）。毎日食べたい。
[ 2 / 46 ]


# Page. 3

![Page Image](https://bcdn.docswell.com/page/L735R2R2E5.jpg)

https://korosuke613.dev
[ 3 / 46 ]


# Page. 4

![Page Image](https://bcdn.docswell.com/page/87DDYZY67G.jpg)

korosuke613.dev | Z
軸の回転
共有
見る
https://korosuke613.dev
[ 4 / 46 ]


# Page. 5

![Page Image](https://bcdn.docswell.com/page/VJPX6D2Z78.jpg)

https://korosuke613.dev
[ 5 / 46 ]


# Page. 6

![Page Image](https://bcdn.docswell.com/page/2EVL31DMJQ.jpg)

Click!
https://korosuke613.dev
[ 5 / 46 ]


# Page. 7

![Page Image](https://bcdn.docswell.com/page/57GX32YXEL.jpg)

Rotation!
https://korosuke613.dev
[ 6 / 46 ]


# Page. 8

![Page Image](https://bcdn.docswell.com/page/4EQ85PM5JP.jpg)

Rotation!
https://korosuke613.dev
[ 7 / 46 ]


# Page. 9

![Page Image](https://bcdn.docswell.com/page/KJ429YVVE1.jpg)

僕が回転します 🙃
[ 8 / 46 ]


# Page. 10

![Page Image](https://bcdn.docswell.com/page/LE1MK6347G.jpg)

さらに...？
[ 9 / 46 ]


# Page. 11

![Page Image](https://bcdn.docswell.com/page/GEW3DWPZE2.jpg)

korosuke613.dev |
ランダムな軸の回転
共有
見る
https://korosuke613.dev
[ 10 / 46 ]


# Page. 12

![Page Image](https://bcdn.docswell.com/page/47ZVG5KLE3.jpg)

回転する方向はランダムです 😵‍💫
[ 11 / 46 ]


# Page. 13

![Page Image](https://bcdn.docswell.com/page/YJ65Y9QM7V.jpg)

クリックするたびに変わります
[ 12 / 46 ]


# Page. 14

![Page Image](https://bcdn.docswell.com/page/GJ5YGNRQJ4.jpg)

https://www.geogebra.org/3d/czwmzrfr
[ 13 / 46 ]


# Page. 15

![Page Image](https://bcdn.docswell.com/page/9E2GY5DWER.jpg)

X
X
軸
軸&amp;Y軸
Y
X
軸
軸&amp;Z軸
Z
Y
軸
軸&amp;Z軸
[ 14 / 46 ]


# Page. 16

![Page Image](https://bcdn.docswell.com/page/D7YVGK297M.jpg)

korosuke613.dev
平木場のホームページ
自分のアウトプットや SNS へのリンクなどの情報をまとめる場所として作った
Astro + React で構成 (二代目)
(余談) 初代は Nuxt + Vue で構成していた
(余談) Nuxt3 移行がつらすぎて新しめのフレームワーク + 業務でも使っている React で作り直すことに
Astro
とは
は、コンテンツにフォーカスした高速なWebサイトを構築するためのオールインワンWebフレームワークです。
https://docs.astro.build/ja/getting-started/ より
Astro
を採用
極力クライアントで JS の実行を減らすように作られているため高速
UI フレームワークとして React を利用できる
MPA (Multi Page Application)
[ 15 / 46 ]


# Page. 17

![Page Image](https://bcdn.docswell.com/page/VENMGR8DJ8.jpg)

ホームページできたけどつまらんな 😑
[ 16 / 46 ]


# Page. 18

![Page Image](https://bcdn.docswell.com/page/Y79NWMK873.jpg)

そうだ。自分を回転させよう 🤩
[ 17 / 46 ]


# Page. 19

![Page Image](https://bcdn.docswell.com/page/G78KNLRZJD.jpg)

どうやって回転させるか
でがんばる
ついでに拡大縮小もさせる(`scale`)
無限に回転してもうざったいので、1 回のみ回転させる
下記の CSS クラスをアイコンの要素に付与する
CSS
例：Z 軸を中心に回転するアニメーションの CSS
.rotate-animation-z {
animation: rotate-anime-z 1.5s linear 1 /*
}
アニメーション名 時間 曲線 繰り返し回数 */
軸を中心に回転するアニメーションの定義
から
までの間をそれぞれ指定
は回転角度、
は拡大縮小
/* Z
*/
/* 0%
100%
*/
/* rotate
scale
*/
@keyframes rotate-anime-z {
0% {transform: rotate(0deg) scale(1);}
25% {transform: rotate(90deg) scale(1.5);}
50% {transform: rotate(180deg) scale(1);}
75% {transform: rotate(270deg) scale(1.5);}
100% {transform: rotate(360deg) scale(1);}
}
[ 18 / 46 ]


# Page. 20

![Page Image](https://bcdn.docswell.com/page/L7LZPLK1ER.jpg)

クリックしたときに回転させたい
このままだとページがロードされた時に 1 回転して終わる
いきなり回られる（しかも 1 回転して止まる）と見てる方が意味わからない
クリックした時に回転させる
クリック時(`click`)に CSS クラスを付与、アニメーション終了時(`animationend`、`animationcancel`)
に CSS クラスを削除する
CSS クラスを削除しないと、最初のクリックでしか回転してくれない
React の `useEffect` を使って、イベントリスナーをロード時に登録する
[ 19 / 46 ]


# Page. 21

![Page Image](https://bcdn.docswell.com/page/4EMV4M25EW.jpg)

クリックしたときに回転させたい
ブラウザ
ユーザ
僕のアイコン
ページのロード
イベントハンドラの登録
loop
クリック時にアニメーションのクラスを付与する
//
thisImg.addEventListener(&#039;click&#039;, () =&gt; {
thisImg.classList.add(cssClassName);
});
僕のアイコンをクリック
(click event)
アニメーションのクラスを付与
(handle click event)
回転
アニメーションの終了
(animationend event)
アニメーションのクラスを削除
(handle animationend event)
ユーザ
const cssClassName = &#039;rotate-animation-z&#039;;
export const MyIcon = (props: IMyIconProps) =&gt; {
useEffect(() =&gt; {
const thisImg = document.getElementById(props.iconId);
if (thisImg === null) return;
ブラウザ
僕のアイコン
アニメーションが終わったらアニメーションのクラスを削除する
//
const removeClass = () =&gt; {
thisImg.classList.remove(cssClassName);
};
thisImg.addEventListener(&#039;animationend&#039;, removeClass);
thisImg.addEventListener(&#039;animationcancel&#039;, removeClass);
});
return (&lt;img /*
*/ /&gt;);
};
省略
[ 20 / 46 ]


# Page. 22

![Page Image](https://bcdn.docswell.com/page/PER2WVMZ79.jpg)

回転した！
[ 21 / 46 ]


# Page. 23

![Page Image](https://bcdn.docswell.com/page/P7XDPZV1JX.jpg)

なんか足りないよな〜 😕
[ 22 / 46 ]


# Page. 24

![Page Image](https://bcdn.docswell.com/page/37KXD8QMJD.jpg)

回転方向を毎回ランダムにしよう 🤣
[ 23 / 46 ]


# Page. 25

![Page Image](https://bcdn.docswell.com/page/LJ35R2P275.jpg)

クリックするたびに回転する方向を変えたい
今のところ Z 軸に 1 回転する動き
しかない
X 軸、Y 軸、Z 軸、XZ 軸、XY 軸、
YZ 軸をランダムに回転させる
アニメーション削除時はどのアニ
メーションクラスが付与されている
かわからないので、全部削除する
const cssClassNames = [
&#039;rotate-animation-x&#039;,
&#039;rotate-animation-y&#039;,
&#039;rotate-animation-z&#039;,
&#039;rotate-animation-xy&#039;,
&#039;rotate-animation-xz&#039;,
&#039;rotate-animation-yz&#039;,
];
export const MyIcon = (props: IMyIconProps) =&gt; {
useEffect(() =&gt; {
const thisImg = document.getElementById(props.iconId);
if (thisImg === null) return;
クリック時にランダムにアニメーションのクラスを付与する
//
thisImg.addEventListener(&#039;click&#039;, () =&gt; {
const randNum = Math.floor(Math.random() * cssClassNames.length);
const cssClassName = cssClassNames[randNum] || &#039;rotate-animation-z&#039;;
thisImg.classList.add(cssClassName);
});
アニメーションが終わったらアニメーションのクラスを削除する
//
const removeClass = () =&gt; {
cssClassNames.forEach((cssClassName) =&gt; {
//
thisImg.classList.remove(cssClassName);
});
};
thisImg.addEventListener(&#039;animationend&#039;, removeClass);
thisImg.addEventListener(&#039;animationcancel&#039;, removeClass);
});
return (&lt;img /*
*/ /&gt;);
};
どのクラスが付与されているかわからないので全部削除する
省略
[ 24 / 46 ]


# Page. 26

![Page Image](https://bcdn.docswell.com/page/8JDDYZL6JG.jpg)

X
軸
Y
軸
Z
軸
回転した！
X
軸&amp;Y軸
X
軸&amp;Z軸
Y
軸&amp;Z軸
[ 25 / 46 ]


# Page. 27

![Page Image](https://bcdn.docswell.com/page/VEPX6D4ZJ8.jpg)

デスクトップ
スマートフォン
実はこのホームページ、レスポンシブ対応している
[ 26 / 46 ]


# Page. 28

![Page Image](https://bcdn.docswell.com/page/27VL31XMEQ.jpg)

2
つのアイコンの表示を画面幅で切り替えている
デスクトップとスマホでアイコンの位置が違うため
&lt;Section&gt;
&lt;div className=&quot;flex flex-col items-center md:flex-row md:justify-between md:gap-x-24&quot;&gt;
&lt;div&gt;
&lt;h1 className=&quot;hidden text-3xl font-bold md:block&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div className=&quot;flex flex-row justify-between md:hidden md:gap-x-24&quot;&gt;
{/*
*/}
&lt;h1 className=&quot;text-3xl font-bold&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div className=&quot;h-20 w-20&quot; id=&quot;my-icon-small&quot;&gt;
&lt;MyIcon /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;p className=&quot;mt-6 text-xl leading-9&quot;&gt; {/*
*/} &lt;/p&gt;
&lt;div className=&quot;mt-3 flex flex-wrap gap-1&quot;&gt; {/* SNS
*/} &lt;/div&gt;
&lt;/div&gt;
&lt;div className=&quot;hidden shrink-0 md:block&quot;&gt;
&lt;div className=&quot;h-72 w-72&quot; id=&quot;my-icon-large&quot;&gt;
&lt;MyIcon /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/Section&gt;
あいさつ
スマホ表示用
あいさつ
小さいアイコン
自己紹介
リンク
大きいアイコン
[ 27 / 46 ]


# Page. 29

![Page Image](https://bcdn.docswell.com/page/5JGX32VX7L.jpg)

2
つのアイコンの表示を画面幅で切り替えている
デスクトップとスマホでアイコンの位置が違うため
&lt;Section&gt;
&lt;div className=&quot;flex flex-col items-center md:flex-row md:justify-between md:gap-x-24&quot;&gt;
&lt;div&gt;
&lt;h1 className=&quot;hidden text-3xl font-bold md:block&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div className=&quot;flex flex-row justify-between md:hidden md:gap-x-24&quot;&gt;
{/*
*/}
&lt;h1 className=&quot;text-3xl font-bold&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div className=&quot;h-20 w-20&quot; id=&quot;my-icon-small&quot;&gt;
&lt;MyIcon /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;p className=&quot;mt-6 text-xl leading-9&quot;&gt; {/*
*/} &lt;/p&gt;
&lt;div className=&quot;mt-3 flex flex-wrap gap-1&quot;&gt; {/* SNS
*/} &lt;/div&gt;
&lt;/div&gt;
&lt;div className=&quot;hidden shrink-0 md:block&quot;&gt;
&lt;div className=&quot;h-72 w-72&quot; id=&quot;my-icon-large&quot;&gt;
&lt;MyIcon /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/Section&gt;
あいさつ
スマホ表示用
あいさつ
小さいアイコン
自己紹介
リンク
大きいアイコン
[ 27 / 46 ]


# Page. 30

![Page Image](https://bcdn.docswell.com/page/47Q85P65EP.jpg)

それぞれのアイコンにアニメーションをつける
ではなくクラス名でアイコンの要
素を取得するようにする
ハンドラーの付与をそれぞれの要素
に行う
Id
&lt;div className=&quot;h-20 w-20 my-icon&quot;&gt;
&lt;MyIcon /&gt; {/*
*/}
&lt;/div&gt;
小さいアイコン
&lt;div className=&quot;h-72 w-72 my-icon&quot;&gt;
&lt;MyIcon /&gt; {/*
*/}
&lt;/div&gt;
大きいアイコン
export const MyIcon = (props: IMyIconProps) =&gt; {
useEffect(() =&gt; {
const icons = document.getElementsByClassName(props.iconClass);
Array.from(icons).forEach((icon) =&gt; {
//
icon.addEventListener(&#039;click&#039;, () =&gt; {
const randNum = Math.floor(Math.random() * cssClassNames.length);
const pickedCssClassName =
cssClassNames[randNum] || &#039;rotate-animation-z&#039;;
icon.classList.add(pickedCssClassName);
});
クリック時にランダムにアニメーションのクラスを付与する
アニメーションが終わったらアニメーションのクラスを削除する
//
const removeClass = () =&gt; {
cssClassNames.forEach((cssClassName) =&gt; {
//
icon.classList.remove(cssClassName);
});
};
icon.addEventListener(&#039;animationend&#039;, removeClass);
icon.addEventListener(&#039;animationcancel&#039;, removeClass);
});
});
どのクラスが付与されているかわからないので全部削除する
[ 28 / 46 ]


# Page. 31

![Page Image](https://bcdn.docswell.com/page/KE429Y4V71.jpg)

ひとつの要素に複数のアニメーションクラスが付与されてしまう。
そのため、出てくるアニメーションの確率が均一にならない。
[ 29 / 46 ]


# Page. 32

![Page Image](https://bcdn.docswell.com/page/L71MK644JG.jpg)

ページのロード
大きいアイコンの`useEffect`
ランダムな
アニメーションクラスの付与
小さいアイコンの`useEffect`
ランダムな
ランダムな
アニメーションクラスの付与 アニメーションクラスの付与
ランダムな
アニメーションクラスの付与
大きいアイコンの要素
小さいアイコンの要素
例: &#039;rotate-animation-z&#039;, &#039;rotate-animation-xy&#039;
例: &#039;rotate-animation-x&#039;, &#039;rotate-animation-y&#039;
コンポーネントそれぞれが、
を持つ要素に対して、
アニメーションクラスをランダムに付与してしまうため。
`MyIcon`
`.my-icon`
[ 30 / 46 ]


# Page. 33

![Page Image](https://bcdn.docswell.com/page/G7W3DWXZ72.jpg)

コンポーネントで 1 要素だけ扱えば良いのでは 🧐
→ 簡単にはいかない理由がある...
1
[ 31 / 46 ]


# Page. 34

![Page Image](https://bcdn.docswell.com/page/4JZVG56L73.jpg)

現在のコンポーネントの呼び出し方
次のような構造で `MyIcon` を呼び出している
Astro (`index.astro`)
├── TSX (`SelfIntroduction.tsx`)
└── TSX (`MyIcon.tsx`)
は Astro から呼び出し、`SelfIntroduction` コンポーネントへ小要素として渡している
`SelfIntroduction.tsx` から `MyIcon.tsx` を呼び出していない理由は Astro の特性に関係する
`MyIcon`
&lt;SelfIntroduction&gt;
&lt;MyIcon
client:idle
iconPath={/*
iconClass=&quot;my-icon&quot;
/&gt;
&lt;/SelfIntroduction&gt;
アイコン画像への PATH */}
[ 32 / 46 ]


# Page. 35

![Page Image](https://bcdn.docswell.com/page/YE65Y92MJV.jpg)

はクライアント上での を極力減らす思想
Astro
JS
Astro はサーバサイドでのレンダリングを可能な限り行うフレームワークであるため、クライアント上での
JS の実行を極力減らすように作られている
したがって、クライアント上で JS を実行するようにするには、`client:*` ディレクティブを宣言しないと
いけない
`client:idle`：ページの初期ロードが完了し、`requestIdleCallback` イベントが発生したら、コンポ
ーネントの JavaScript をロードしてハイドレートする
[1]
[2]
例（`index.astro`）
&lt;SelfIntroduction&gt;
&lt;MyIcon
client:idle {/*
iconPath={/*
iconClass=&quot;my-icon&quot;
/&gt;
&lt;/SelfIntroduction&gt;
これがないと動かない */}
アイコン画像への PATH */}
1. https://docs.astro.build/en/concepts/why-astro/#server-first
2. https://docs.astro.build/en/reference/directives-reference/#clientidle
[ 33 / 46 ]


# Page. 36

![Page Image](https://bcdn.docswell.com/page/GE5YGN2QE4.jpg)

MyIcon
に
client:*
を適用しないといけない
はゴリゴリ JS を使っているため、`client:*` の設定が必要
`client:*` は `*.astro` のみから呼び出せる
`client:*` の効果は付与したコンポーネントの子コンポーネントに及ぶ
`MyIcon`
方法
1. `SelfIntroduction`
ごと `client:idle` 付与
Astro (`index.astro`)
└── TSX (`SelfIntroduction.tsx`) `client:idle`
└──
2. `MyIcon`
TSX (`MyIcon.tsx`)
付与
のみに `client:idle` を付与（現在の方法）
Astro (`index.astro`)
├── TSX (`SelfIntroduction.tsx`)
└── TSX (`MyIcon.tsx`) `client:idle`
付与。`SelfIntroduction` に渡す
[ 34 / 46 ]


# Page. 37

![Page Image](https://bcdn.docswell.com/page/972GY54W7R.jpg)

自己紹介セクションが一瞬出てくるがすぐ消えてしまう... 🫨
「1. `SelfIntroduction` ごと `client:idle` 付与」
の方法はなんかうまくいかなかった。
（原因を特定するに至らず...）
[ 35 / 46 ]


# Page. 38

![Page Image](https://bcdn.docswell.com/page/DJYVGKM9JM.jpg)

ここまでの整理
現在、`MyIcon` を Astro から呼び出し、`SelfIntroduction` に渡している
この方法ではアニメーションの確率が均一にならないので 1 コンポーネント 1 要素にしたい
`SelfIntroduction` から `MyIcon` を呼び出すことで 1 コンポーネント 1 要素に簡単にできる
が、この方法だと描画がおかしくなる
どうするか
[ 36 / 46 ]


# Page. 39

![Page Image](https://bcdn.docswell.com/page/V7NMGRWDE8.jpg)

ここまでの整理
現在、`MyIcon` を Astro から呼び出し、`SelfIntroduction` に渡している
この方法ではアニメーションの確率が均一にならないので 1 コンポーネント 1 要素にしたい
`SelfIntroduction` から `MyIcon` を呼び出すことで 1 コンポーネント 1 要素に簡単にできる
が、この方法だと描画がおかしくなる
どうするか
の `useState` を使って複数の `MyIcon` が複数の要素を扱ってもなんとかなるようにする
`MyIcon` 呼び出し含む `SelfIntroduction.tsx` を `.astro` ファイル化
React
[ 36 / 46 ]


# Page. 40

![Page Image](https://bcdn.docswell.com/page/YJ9NWMX8J3.jpg)

の useState を使って複数の MyIcon が複
数の要素を扱ってもなんとかなるようにする
React
以前はこの方法を使っていた。資料を作ってるうちにやめた
`useState` と `useRef` で大きいアイコンと小さいアイコンそれぞれの現在のアニメーションを状態とし
て管理
大小アイコンそれぞれにアニメーションクラスが 1 つのみ追加されるようになった
結構複雑
（当時はなんか色々試してる内にできてしまったが、どうして `useRef` が必要だったかが思い出せない）
[ 37 / 46 ]


# Page. 41

![Page Image](https://bcdn.docswell.com/page/GJ8KNL2ZED.jpg)

`MyIcon.tsx`
export const MyIcon = (props: IMyIconProps) =&gt; {
const [largeMode, setLargeMode] = useState(&#039;rotate-animation-z&#039;);
const largeModeRef = useRef&lt;string&gt;(null!);
largeModeRef.current = largeMode;
const [smallMode, setSmallMode] = useState(&#039;rotate-animation-z&#039;);
const smallModeRef = useRef&lt;string&gt;(null!);
smallModeRef.current = smallMode;
useEffect(makeEffect(&#039;my-icon-large&#039;, setLargeMode, largeModeRef));
useEffect(makeEffect(&#039;my-icon-small&#039;, setSmallMode, smallModeRef));
return (
&lt;img
src={props.iconPath}
style={{ width: &#039;100%&#039; }}
alt=&quot;Avatar image&quot;
loading=&quot;lazy&quot;
/&gt;
);
};
供養 🪦
[ 38 / 46 ]


# Page. 42

![Page Image](https://bcdn.docswell.com/page/LJLZPL217R.jpg)

`MyIcon.tsx`
const makeEffect = (
id: string,
setMode: React.Dispatch&lt;React.SetStateAction&lt;string&gt;&gt;,
modeRef: React.MutableRefObject&lt;string&gt;
) =&gt; {
return () =&gt; {
const thisImg = document.getElementById(id);
if (thisImg === null) return () =&gt; {};
const start = () =&gt; {
cssNames.forEach((c) =&gt; {
thisImg.classList.remove(c);
});
const randNum = Math.floor(Math.random() * cssNames.
const cssName = cssNames[randNum] || &#039;rotate-animati
setMode(cssName);
thisImg.classList.add(modeRef.current);
};
thisImg.addEventListener(&#039;click&#039;, start);
thisImg.addEventListener(&#039;animationend&#039;, end);
thisImg.addEventListener(&#039;animationcancel&#039;, end);
return () =&gt; {
thisImg.removeEventListener(&#039;click&#039;, start);
thisImg.removeEventListener(&#039;animationend&#039;, end);
thisImg.removeEventListener(&#039;animationcancel&#039;, end);
};
};
};
供養 🪦
const end = () =&gt; {
cssNames.forEach((cssName) =&gt; {
thisImg.classList.remove(cssName);
});
};
[ 39 / 46 ]


# Page. 43

![Page Image](https://bcdn.docswell.com/page/47MV4M857W.jpg)

MyIcon
.astro
呼び出し含む
ファイル化
SelfIntroduction.tsx
を
資料作ってるうちにこっちの方がシンプルで説明しやすいことに気づいて採用
`MyIcon` を 1 コンポーネント 1 要素にするよう修正
Id を外から渡す形式に変更（大きいアイコンの見回していた初期の内容）
`SelfIntroduction.tsx` を `.astro` の形式に書き換える
`index.astro` から `SelfIntroduction.astro` を呼び出す
新たにできた `SelfIntroduction.astro` から `MyIcon` を呼び出し、`MyIcon` に `client:idle` を付
与する
[ 40 / 46 ]


# Page. 44

![Page Image](https://bcdn.docswell.com/page/P7R2WV5ZJ9.jpg)

`SelfIntroduction.astro`
--// &lt;
:
&gt;
import { MyIcon } from &#039;@/components/MyIconRandomLargeOnly&#039;;
const iconPath = /*
*/;
// &lt;
:
&gt;
--&lt;Section&gt;
&lt;div className=&quot;flex flex-col items-center md:flex-row md:justify-between md:gap-x-24&quot;&gt;
&lt;div&gt;
&lt;h1 className=&quot;hidden text-3xl font-bold md:block&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div className=&quot;flex flex-row justify-between md:hidden md:gap-x-24&quot;&gt;
{/*
*/}
&lt;h1 className=&quot;text-3xl font-bold&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div class=&quot;my-icon h-20 w-20&quot; id=&quot;my-icon-small&quot;&gt;
&lt;MyIcon client:idle iconPath={iconPath} iconId=&quot;my-icon-small&quot; /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;p className=&quot;mt-6 text-xl leading-9&quot;&gt; {/*
*/} &lt;/p&gt;
&lt;div className=&quot;mt-3 flex flex-wrap gap-1&quot;&gt; {/* SNS
*/} &lt;/div&gt;
&lt;/div&gt;
&lt;div className=&quot;hidden shrink-0 md:block&quot;&gt;
&lt;div class=&quot;my-icon h-72 w-72&quot; id=&quot;my-icon-large&quot;&gt;
&lt;MyIcon client:idle iconPath={iconPath} iconId=&quot;my-icon-large&quot; /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/Section&gt;
省略 色々なインポート
アイコンのパス
省略 その他処理
あいさつ
スマホ表示用
あいさつ
小さいアイコン
自己紹介
リンク
大きいアイコン
[ 41 / 46 ]


# Page. 45

![Page Image](https://bcdn.docswell.com/page/PJXDPZK1EX.jpg)

`SelfIntroduction.astro`
--// &lt;
:
&gt;
import { MyIcon } from &#039;@/components/MyIconRandomLargeOnly&#039;;
const iconPath = /*
*/;
// &lt;
:
&gt;
--&lt;Section&gt;
&lt;div className=&quot;flex flex-col items-center md:flex-row md:justify-between md:gap-x-24&quot;&gt;
&lt;div&gt;
&lt;h1 className=&quot;hidden text-3xl font-bold md:block&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div className=&quot;flex flex-row justify-between md:hidden md:gap-x-24&quot;&gt;
{/*
*/}
&lt;h1 className=&quot;text-3xl font-bold&quot;&gt; {/*
*/} &lt;/h1&gt;
&lt;div class=&quot;my-icon h-20 w-20&quot; id=&quot;my-icon-small&quot;&gt;
&lt;MyIcon client:idle iconPath={iconPath} iconId=&quot;my-icon-small&quot; /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;p className=&quot;mt-6 text-xl leading-9&quot;&gt; {/*
*/} &lt;/p&gt;
&lt;div className=&quot;mt-3 flex flex-wrap gap-1&quot;&gt; {/* SNS
*/} &lt;/div&gt;
&lt;/div&gt;
&lt;div className=&quot;hidden shrink-0 md:block&quot;&gt;
&lt;div class=&quot;my-icon h-72 w-72&quot; id=&quot;my-icon-large&quot;&gt;
&lt;MyIcon client:idle iconPath={iconPath} iconId=&quot;my-icon-large&quot; /&gt; {/*
*/}
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/Section&gt;
省略 色々なインポート
アイコンのパス
省略 その他処理
あいさつ
スマホ表示用
あいさつ
小さいアイコン
自己紹介
リンク
大きいアイコン
[ 41 / 46 ]


# Page. 46

![Page Image](https://bcdn.docswell.com/page/3JKXD85MED.jpg)

index.astro
--// &lt;
:
&gt;
import SelfIntroduction from &#039;@/components/SelfIntroduction.astro&#039;;
省略 色々なインポート
省略 その他処理
// &lt;
:
&gt;
--...
&lt;SelfIntroduction /&gt;
...
[ 42 / 46 ]


# Page. 47

![Page Image](https://bcdn.docswell.com/page/LE35R2K2J5.jpg)

1
つのアイコンに複数のアニメーションクラスが付与されなくなった 🥳
[ 43 / 46 ]


# Page. 48

![Page Image](https://bcdn.docswell.com/page/8EDDYZ36EG.jpg)

.astro
ファイル化してみて
良かったこと
`.tsx` を `.astro` に置き換えるのは簡単だった
`.astro` になることで動的に JS を実行したい場合に実装がシンプルになった
イマイチなこと
`.tsx` から `.astro` を呼び出せない
今後困ることがあるかもしれない
実際に動的にしたい部分だけ `.tsx` にしておいて、配置の際は `.astro` でラップしたものを使えば
良さそう
[ 44 / 46 ]


# Page. 49

![Page Image](https://bcdn.docswell.com/page/V7PX6DPZE8.jpg)

まとめ
を駆使して画像を回転させられる
クリックするたび回転させたかったら、回転する CSS をクリック時に付与、アニメーション終了時に削除
すればできる
ランダムに回転させたい、かつ、なんらかの理由で同じ画像のコンポーネントを複数配置したい、かつ、
Astro を使っている場合は頭を捻る必要がある
Astro で動的に JS を実行したい場合は `client:*` の書きどころに注意
動的なコンポーネントを呼び出す部分を `.astro` にするのも良さげ
CSS
もっといい方法を知っている人はぜひ教えてください！！
[ 45 / 46 ]


# Page. 50

![Page Image](https://bcdn.docswell.com/page/2JVL312M7Q.jpg)

ご清聴ありがとうございました！
ちなみに、このスライドは Slidev で作成しました 。
[1]
[2]
1. https://sli.dev/
2. https://github.com/korosuke613/zenn-articles/pull/376
[ 46 / 46 ]


