背景に溶け込まないように前景要素を出し分ける

  • プロダクション
背景に溶け込まないように前景要素を出し分ける

目次

Webページ上にある固定パーツ

Webページには固定配置される(ページをスクロールしても同じ位置に留まり続ける)パーツがよくあります。共通ヘッダーとか、スクロール追従メニューとか、「ページトップに戻る」ボタンなどがよく固定配置されますね。長いページのどこを見ていても機能にアクセスできるので重宝です。

通常 position : fixed などで位置を固定するこれらのは固定パーツは、背景色とのコントラストが小さいと溶け込んで見にくくなってしまうことが往々にしてあります。

そこで、明るい背景色の時には前景色を暗く、背景が濃暗色の時には前景色を明るく、と、背景に応じて見やすいパーツを出し分けられないか、というのが本稿のテーマです。

以下、JavaScript を用いる方法と、CSS だけを使う方法の2種類をご紹介します。

JavaScript で出し分ける

背景の変化を検知するのにうってつけの JavaScript インターフェイスがあります。Intersection Observer API(交差オブザーバーAPI)です。スクロールなどで画面内に進入してきた要素を検知することができます(こちらも IE 終了で自由に使えるようになった機能のひとつです)。

交差オブザーバーAPI

HTML/CSS

ロゴとセクションはそれぞれ明・暗2種ずつ用意しました。もちろん前景・背景の組み合わせはもっと多様にしてもかまいません。

ロゴ部分のCSSは、親の class を変更すると連動して子(ロゴ)の表示/非表示が切り替わるように整えておきます。こうしておけば、JavaScript 側から操作する要素が親要素だけで済みます。display でコントロールしてもよいですが、opacity ならば transition で表示/非表示を滑らかに切り替えることができます。

<div id="logoArea">
    <img src="./assets/images/logo-bright.svg" alt="トライベック水産" class="logo bright-logo" /><!-- 明るいロゴ -->
    <img src="./assets/images/logo-dark.svg" alt="トライベック水産" class="logo dark-logo" /><!-- 暗いロゴ -->
</div>
<section class="dark-background">暗い背景のセクション</section>
<section class="bright-background">明るい背景のセクション</section>
<section class="dark-background">暗い背景のセクション</section>
<section class="bright-background">明るい背景のセクション</section>
:
:

/* SASS記法です */
.logo-area {
    position: fixed;
    .logo {
        position: absolute;
        opacity: 1;
        transition: .2s;
    }
    &.dark-foreground {
        .dark-logo {
            opacity:0;
        }
    }
    &.bright-foreground {
        .bright-logo {
            opacity:0;
        }
    }
}
section{
    .dark-background {
        background-color: #555;
    }
    .bright-background {
        background-color: #eee;
    }
}

JavaScript

スクリプトは以下のような処理をします。

  1. 画面が前後にスクロールして隣のセクションがロゴにさしかかると、
  2. そのセクションの class を取得し、
  3. 対応する class をロゴに付与する

// ルートを定義(※実際にはビューポートを使うので、直接のルートではない)
const root = document.getElementById('logoArea');
// ルートの範囲を計算(画面の下端からロゴの下端までの距離を計算)
// (「65」はロゴの上部マージン+ロゴの高さ)
const rootMarginBottom = (1 - (65 / window.innerHeight)) * -100 + '%';
    
//監視オプション
const options = {
    root: null,  // ビューポート
    rootMargin: '-15px 0% ' + rootMarginBottom + ' 0%', // 監視領域の指定
    threshold: 0, // 閾値。0 は「少しでも重なったら発火」を意味する
}
    
// Intersection Observer を作成
const observer = new IntersectionObserver(callback, options);
    
// 交差検知した時に呼び出されるコールバック (1)
function callback(entries) {
    entries.forEach((entry) => {
        // 交差検知したセクションの class を取得 (2)
        if (entry.isIntersecting) {
            // それに応じた class をロゴに付与 (3)
            if (entry.target.classList.contains('dark-background')) {
                root.classList.remove('bright-foreground');
                root.classList.add('dark-foreground');
            } else if (entry.target.classList.contains('bright-background')) {
                root.classList.remove('dark-foreground');
                root.classList.add('bright-foreground');
            }
        }
    });
};
    
// 各セクションの監視を開始(forEach でひとつひとつに)
let targets = document.querySelectorAll('section');
targets.forEach(element => {
    observer.observe(element);
});

Intersection Observer の使い方について詳しい解説は省略しますが、今回はロゴ(前景)とセクション(背景)が兄弟要素であるところに注意が必要です。Intersection Observer のルート(監視元)とターゲット(監視先)は親子関係でなければならず、兄弟要素は互いに監視し合うことができないからです。

そこで、ルートはビューポートとし、ロゴが配置されている範囲を option で指定します。ビューポートは高さを指定することができませんので、今回はブラウザ表示領域の下端からの高さを動的に算出して rootMargin を設定することにしました。

結果

動作サンプルはこちら

CSS だけで出し分ける方法

CSS だけで背景の明暗に対応したロゴを出し分けるのに、今回は background-attachement: fixed を使うことにします。

HTML/CSS

<section class="dark-background">暗い背景のセクション</section>
<section class="bright-background">明るい背景のセクション</section>
<section class="dark-background">暗い背景のセクション</section>
<section class="bright-background">明るい背景のセクション</section>
:
:

/* SASS記法です */
section {
    &.dark-background {
        background-color: #333;
    }
    &.bright-background {
        background-color: #eee;
    }
    &::after {
        content: "";
        display: block;
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        background-attachment: fixed;
        background-repeat: no-repeat;
        background-position: 15px 15px;
        background-size: 300px 50px;
        pointer-events: none;
    }
    &.dark-background::after {
        background-image: url(../images/logo-bright.svg);
    }
    &.bright-background::after {
        background-image: url(../images/logo-dark.svg);
    }
}

ロゴはHTML上の要素としては存在せず、セクション最前面を覆う透明な ::after 疑似要素の背景として配置しています。このままでは ::after 疑似要素に阻まれてユーザーがセクションのテキストやリンクなどに手を触れることができないため、pointer-events: none でクリックが貫通するようにしています。

この方法では、境界線上でくっきりとロゴが反転します。実装はシンプルなのですが、ちょっと不思議な効果が出ますね。

なお、ロゴが要素でなければならない場合(リンクにしたい時など)は、position: fixed で透明なボックスをロゴの位置に配置するとよいでしょう。

結果

動作サンプルはこちら

メリット・デメリット

JavaScript の実装例は、ロゴが要素として独立しているので HTMLソース と見た目が一致しているのが利点です。反面、出し分けたい要素がロゴ以外にも画面上のあちらこちらに複数ある場合はそれぞれに Intersection Observer を設定する必要があり、増やしたり減らしたりするのに手間がかかるのが難点です。

一方ピュア CSS での実装例は、JavaScript のスキルが不要なところ、ロゴが明暗境界線上できれいに切り替わるところ、パーツの増減も background-image を複数指定すればよく簡単なところなどです。反面、背景として表示しているのでそれ自体は要素を持たないことや、background-attachment: fixed はスマートフォン用ブラウザでの利用に難があるため、スマホサイトに適用できないことなどが難点です。

Can I Use - background-attachment

この記事の執筆者

G・I

テクノロジーソリューション事業部

この記事に関するご相談やご質問など、お気軽にお問い合わせください。

お問い合わせ

タグ一覧