☕はじめに
こんばんは、ここあです。最近ポートフォリオサイト(my-portfolio-urr.pages.dev)を作っていて、地味にハマるバグに遭遇しました。
「スクロールしたらアイコンが消える」
最初は「え、なんで??」ってなりました。読み込み時は完璧に表示されてるのに、スクロールした瞬間にGitHubやXのアイコンが綺麗さっぱり消えてしまいます。リロードするとまた表示される。でもスクロールすると消える。はて...
同じ問題に遭遇している人の助けになればと思い、原因と解決法をまとめておきます。
🎨 ポートフォリオサイトについて
まず、今回問題が発生したのは自分のポートフォリオサイトです。シンプルでモダンな感じを目指して、以下のような構成で作りました:
- フレームワーク: React(CDN版)
- スタイリング: Tailwind CSS
- アイコン: Lucide Icons
- 特徴: SPA形式で複数ページを切り替え可能
技術スタックや制作物、記事一覧などを載せてる、よくあるポートフォリオサイトですね。デザインは黒とグレーをベースにしたミニマルな感じにしてます。
🐛 発生した問題
サイトを作っていて、こんな症状が出ました:
- ページ読み込み時:すべてのアイコンが正常に表示される
- スクロール開始:突然アイコンが消える
- ページ再読み込み:アイコンが復活する
- またスクロール:また消える
ナビゲーションのアイコンやSNSリンクのアイコンが、スクロールのたびに消えるという、ユーザー体験的には最悪な状態になってました
🔍 原因の調査
まず、「Lucide Icons スクロール 消える」でググってみたんですが、ドンピシャな情報があまりなくて。DevToolsを開いて、DOM要素を見てみると...
<!-- スクロール前 -->
<i data-lucide="github" width="24" height="24">
<svg>...</svg>
</i>
<!-- スクロール後 -->
<i data-lucide="github" width="24" height="24"></i>
<!-- SVGが消えてる! -->
ああ、これか。<i>タグは残ってるのに、中身のSVGが消えてる。
💡 原因判明
問題の核心はLucide IconsとReactの再レンダリングの相性問題でした。
Lucide Iconsの仕組み
Lucide Iconsはlucide.createIcons()を呼ぶと、DOM内のdata-lucide属性を持つ要素を探して、その中にSVGを挿入するという仕組みです。
// 初回読み込み時
useEffect(() => {
lucide.createIcons(); // ここでアイコンが描画される
}, []);
問題の核心
今回のサイトでは、スクロール時にヘッダーの背景を変える処理を入れてました:
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 20); // スクロールで状態変更
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// ヘッダーのclassNameが動的に変わる
<header className={`${scrolled ? 'bg-white/80 backdrop-blur-md' : 'bg-transparent'}`}>
このscrolledステートが変わると、Reactは仮想DOMを使って効率的に再レンダリングします。この時、Lucideが挿入したSVG要素がReactの管理外なので、再レンダリング時に削除されてしまうんです。
つまり:
- 初回レンダリング →
lucide.createIcons()でSVG挿入 - スクロール →
scrolled変更 → React再レンダリング - Reactが「この
<i>タグの中身、俺が管理してないから消すわ」→ SVG消滅
という流れだったわけです。
🛠️ 解決方法
答えはシンプル。状態が変わるたびにアイコンを再描画すればいい。
// スクロール状態が変わるたびにアイコンを再描画
useEffect(() => {
lucide.createIcons();
}, [scrolled, mobileMenuOpen]); // 依存配列に状態を追加
これだけ!
完全なコード例
const PortfolioSite = () => {
const [currentPage, setCurrentPage] = useState('home');
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
// ページ切り替え時
useEffect(() => {
lucide.createIcons();
}, [currentPage]);
// スクロール監視
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 20);
};
window.addEventListener('scroll', handleScroll);
lucide.createIcons(); // 初期描画
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// ここが重要!状態変化時の再描画
useEffect(() => {
lucide.createIcons();
}, [scrolled, mobileMenuOpen]);
// ... 以下略
};
📝 学んだこと
1. ReactとDOM操作ライブラリの相性問題
ReactはVirtual DOMで効率的に更新を行いますが、その分「Reactの管理外」のDOM操作には注意が必要です。
- jQuery
- D3.js
- Lucide Icons
- その他直接DOMを操作するライブラリ
これらを使う時は、Reactの再レンダリングタイミングを意識する必要があります。
2. useEffectの依存配列の重要性
今回のケースでは、「どの状態が変わったらアイコンを再描画すべきか」を依存配列で明示することで解決しました。
useEffect(() => {
lucide.createIcons();
}, [scrolled, mobileMenuOpen, currentPage]); // これらが変わったら再描画
3. パフォーマンスへの配慮
lucide.createIcons()は比較的軽量ですが、スクロールのたびに呼ばれるのは理想的ではありません。本当はこういう状態管理をするなら、React Iconsなど、Reactネイティブなアイコンライブラリを使うべきかもしれません。
ただ、小規模なサイトなら気にならないレベルです。
🎯 まとめ
- Lucide IconsはDOM操作でSVGを挿入する
- Reactの再レンダリングでDOM操作の結果が消えることがある
- 状態変更時に
lucide.createIcons()を再実行すれば解決 - useEffectの依存配列を適切に設定することが重要
🚀 代替案
もし同じような問題に悩んでいるなら、こんな選択肢もあります:
1. React Icons を使う
import { FaGithub, FaTwitter } from 'react-icons/fa';
// こっちはReactコンポーネントなので問題なし
<FaGithub size={24} />
2. SVGを直接インポート
import GithubIcon from './icons/github.svg';
<img src={GithubIcon} alt="GitHub" />
3. Lucide Reactパッケージを使う
import { Github, Twitter } from 'lucide-react';
// こっちもReactコンポーネント
<Github size={24} />
ただ、今回はCDN版で手軽に実装したかったので、Vanilla JSのLucideを使い続けることにしました。
おわりに
地味にハマるバグほど、解決したときの達成感ありますよね。今回は原因さえわかれば1行で解決する問題でしたが、そこに辿り着くまでが大変でした。
同じ問題に遭遇した誰かの役に立てば嬉しいです。ポートフォリオサイトはまだまだ改善中なので、何か気づいたことがあればぜひフィードバックください!
では、また👋
関連リンク
筆者について
フルスタックエンジニア。Web開発を中心に活動中。技術ブログで学びをシェアしています。
- GitHub: @CocoaAI-IT
- X: @CocoaData