0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スクロールするとアイコンが消える?Lucide Iconsで遭遇した地味にハマるバグと解決法

Posted at

☕はじめに

こんばんは、ここあです。最近ポートフォリオサイト(my-portfolio-urr.pages.dev)を作っていて、地味にハマるバグに遭遇しました。

「スクロールしたらアイコンが消える」

最初は「え、なんで??」ってなりました。読み込み時は完璧に表示されてるのに、スクロールした瞬間にGitHubやXのアイコンが綺麗さっぱり消えてしまいます。リロードするとまた表示される。でもスクロールすると消える。はて...

同じ問題に遭遇している人の助けになればと思い、原因と解決法をまとめておきます。

🎨 ポートフォリオサイトについて

まず、今回問題が発生したのは自分のポートフォリオサイトです。シンプルでモダンな感じを目指して、以下のような構成で作りました:

  • フレームワーク: React(CDN版)
  • スタイリング: Tailwind CSS
  • アイコン: Lucide Icons
  • 特徴: SPA形式で複数ページを切り替え可能

技術スタックや制作物、記事一覧などを載せてる、よくあるポートフォリオサイトですね。デザインは黒とグレーをベースにしたミニマルな感じにしてます。

🐛 発生した問題

サイトを作っていて、こんな症状が出ました:

  1. ページ読み込み時:すべてのアイコンが正常に表示される
  2. スクロール開始:突然アイコンが消える
  3. ページ再読み込み:アイコンが復活する
  4. またスクロール:また消える

ナビゲーションのアイコンや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の管理外なので、再レンダリング時に削除されてしまうんです。

つまり:

  1. 初回レンダリング → lucide.createIcons()でSVG挿入
  2. スクロール → scrolled変更 → React再レンダリング
  3. 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開発を中心に活動中。技術ブログで学びをシェアしています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?