カルキチブログ

Reactあるある!?画像読み込み時のスタイル崩れを解決する

タイトルの内容そのまんまなのですが、みなさんこういう経験ありませんでしょうか?

僕のブログ場合は、プロフィールのカルキチアイコンがコンポーネントがレンダリングされた後に、スタイルが読み込まれるからなのか、カルキチが巨大化(正確には保存時の大きさで画像が読み込まれる)→時間差でスタイルが読み込みこまれる→元の大きさに戻るというカオスなことになっていたので、解決策を考えました。

今回は正攻法なのかどうかはわかりませんが、この現象の解決策を見つけたのでそれについて書いていきます。

解決した方法

react-lazy-load-image-componentというライブラリを使って解決しました。

以下公式の引用です。

React Component to lazy load images and other components/elements. Supports IntersectionObserver and includes a HOC to track window scroll position to improve performance.

引用:https://www.npmjs.com/package/react-lazy-load-image-component

画像やその他のReactコンポーネントや要素を遅延読み込みさせることができる??的な意味だと思います。

→僕の英語力は英検3級レベルで止まっています。センター英語は100点切るか切らないかの瀬戸際でした。。。

プロフィール部分のコンポーネントはこんな感じになっています。

import React from 'react';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import { H4 } from '../../styled-components/atoms/Heading';
import { SidebarBox } from '../../styled-components/BlogSidebar';

const Profile: React.FC = () => {
    return(
        <SidebarBox>
            <H4>プロフィール</H4>
            <div className="profile-area">
                <LazyLoadImage
                    className="profile-icon"
                    src='/icon.png'
                    alt='icon'
                    effect="blur"
                />
                <p>カルキチ副島です。</p>
                <p>都内でウェブ系の開発やっています。</p>
                <p>普段開発しているものや、日常について書いています。</p>
                <p>よろぴ</p>
            </div>
        </SidebarBox>
    );
}

export default Profile;

公式まんまです。

何も特殊なことはしていません。

effect="blur"のスタイルは、アプリケーションの<html>および<body>タグを拡張することができるDocumentコンポーネントで読み込んでいます。

import React from 'react';
import Document, { DocumentContext, Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
    static async getInitialProps(context: DocumentContext) {
        const sheet = new ServerStyleSheet();
        const originalRenderPage = context.renderPage;

        context.renderPage = () =>
            originalRenderPage({
                enhanceApp: (App: any) => (props :any) => 
                    sheet.collectStyles(<App {...props} />),
        });

        const initialProps = await Document.getInitialProps(context);

        return {
            ...initialProps,
            styles: [...(initialProps.styles as any), ...sheet.getStyleElement()]
        };
    };

    public render() {
        return (
            <Html lang="ja">
                <Head>
                    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
                    <style>{
                        `html,body,h1,h2,h3,h4,p {
                            padding: 0;
                            margin: 0;
                        }
                        body {
                            font-family: -apple-system,BlinkMacSystemFont,Helvetica Neue,YuGothic,ヒラギノ角ゴ ProN W3,Hiragino Kaku Gothic ProN,Arial,メイリオ,Meiryo,sans-serif;
                            line-height: 1.5;
                            color: #2b2c30;
                        }
                        a {
                            text-decoration: none;
                        }
                        .lazy-load-image-background.blur {
                            filter: blur(15px);
                        }
                        .lazy-load-image-background.blur.lazy-load-image-loaded {
                            filter: blur(0);
                            transition: filter .3s;
                        }
                        .lazy-load-image-background.blur > img {
                            opacity: 0;
                        }
                        .lazy-load-image-background.blur.lazy-load-image-loaded > img {
                            opacity: 1;
                            transition: opacity .3s;
                        }
                        `
                    }</style>
                </Head>
                <body>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }
}

export default MyDocument;

公式ではコンポーネント内部でスタイルを読み込んでいましたが、TypeScriptではエラーが起きたので、headで読み込むようにしています。

僕は面倒臭がりなので、リセットCSSもここで指定しています。

ちなみに、プロフィールは関数コンポーネントなのに、_document.tsxはクラスコンポーネントになっている理由は、Documentコンポーネントを使用する場合はNext.jsの仕様上、NextDocumentというクラスを継承しないと使えないからです。

GithubのNext.jsで作られたアプリケーションのリポジトリを流し見た感じ、Documentコンポーネントを関数コンポーネントで書いていたものはなかったです。

公式もクラスコンポーネントになっていました。

【Next.jsの公式】

https://nextjs.org/docs/advanced-features/custom-document

これはおそらくなので、書き方次第ではもしかしたら関数コンポーネントでもいけるかもしれないです。

まとめ

特に何か面倒なことをしなくても、画像の遅延読み込みができるので、結構良きって感じです。

ただ、コンポーネントや要素しか効かなそうなので、Markdown内の画像に対して、個別に遅延読み込みをかけるとかは多分無理だと思います。

特定の画像に対してや、JSONオブジェクトにある画像のパスを取得して、mapとかでループさせつつ、取得した画像に対して遅延読み込みをかけるというのが主なユースケースなのかなって感じです。

おまけ

全体的にまだまだ作りが甘い部分が多いので、もうちょい頑張りたいです。

特に記事の部分は、もうちょい読みやすくしたいものです。

あと日本語力もつけたいです。

もう若人とは言えない年齢なのにマジとかヤバイ、パナイなど若者言葉が抜けないです。

あーマジヤバ