この記事について
備忘録。各サンプルファイルを残しておく。
html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>背景追従</title>
<link rel="stylesheet" href="./assets/css/style.css">
<script src="./assets/js/script.js" defer></script>
</head>
<body>
<header>
<p>ヘッダーです</p>
</header>
<main>
<div class="announcement-area">
<p>お知らせエリアです</p>
</div>
<section class="scene" id="js-scene">
<div class="scene__background" id="js-background">
<img src="./assets/bg_pc.JPG" alt="">
</div>
<div class="scene__content" id="js-content">
<h2>コンテンツタイトル</h2>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
<section>
<p>高さが変化するコンテンツです。</p>
</section>
</div>
</section>
</main>
<footer>
<p>footerです</p>
</footer>
</body>
</html>
css
@charset "utf-8";
html,
body,
*::before,
*::after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
font-family: sans-serif;
font-size: 62.5%;
color: #333;
font-weight: 400;
line-height: 1.2;
}
body {
font-size: 1.6rem;
}
header {
background: #fff;
height: 60px;
width: 100%;
border-bottom: 1px solid #333;
display: flex;
align-items: center;
}
footer {
background: #fff;
height: 100px;
width: 100%;
border-top: 1px solid #333;
display: flex;
align-items: center;
}
header p,
footer p,
.announcement-area p,
.content-area p {
width: fit-content;
margin: auto;
font-weight: 700;
font-size: 2.6rem;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.scene__content section {
height: 200px;
}
/* ここから ============================== */
html,
body {
overflow-x: clip;
}
.scene {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr);
isolation: isolate;
}
.scene__background,
.scene__content {
grid-area: 1 / 1;
align-self: start;
min-width: 0;
}
.scene__background {
position: relative;
z-index: 0;
overflow: visible;
}
/* 背景画像を一時固定 */
.scene__background.is-sticky {
position: sticky;
top: var(--sticky-top, 0px);
}
/* コンテンツが画像より短い場合 */
.scene__background.is-cropped {
overflow: hidden;
}
.scene__background img {
display: block;
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: max(100%, 1366px);
max-width: none;
height: auto;
}
.scene__content {
position: relative;
z-index: 1;
padding: 80px 5%;
}
footer {
position: relative;
z-index: 2;
}
/* ここまで ============================== */
js
'use strict';
const scene = document.getElementById('js-scene');
const background = document.getElementById('js-background');
const content = document.getElementById('js-content');
const image = background.querySelector('img');
// 要素の寸法とstickyの位置を計算
const measure = () => {
if (!image.naturalWidth || !image.naturalHeight) {
return;
}
// 実際の表示高さを取得
const imageHeight = image.getBoundingClientRect().height;
const contentHeight = content.getBoundingClientRect().height;
if (!imageHeight) return;
// コンテンツと画像のうち小さい方を表示領域にする
const displayHeight = Math.min(
imageHeight,
contentHeight
);
// 背景画像の表示領域を確保
const currentHeight =
background.getBoundingClientRect().height;
if (Math.abs(currentHeight - displayHeight) > 0.5) {
background.style.height = `${displayHeight}px`;
}
// コンテンツが画像より短い場合はトリミング
const shouldCrop = contentHeight < imageHeight;
background.classList.toggle(
'is-cropped',
shouldCrop
);
// コンテンツが画像より長い場合だけstickyを有効化
const shouldStick = contentHeight > imageHeight;
background.classList.toggle(
'is-sticky',
shouldStick
);
if (shouldStick) {
// セクションの文書内開始位置
const sceneTop =
scene.getBoundingClientRect().top + window.scrollY;
// 背景画像の下端がビューポートに入った位置
const stickyTop = Math.min(
sceneTop,
window.innerHeight - imageHeight
);
background.style.setProperty(
'--sticky-top',
`${stickyTop}px`
);
} else {
background.style.removeProperty('--sticky-top');
}
};
// サイズの変化を監視
const resizeObserver = new ResizeObserver(measure);
resizeObserver.observe(image);
resizeObserver.observe(content);
// ビューポートのサイズ変更
window.addEventListener('resize', measure);
// 画像読み込み後に初期化
if (image.complete && image.naturalWidth) {
measure();
} else {
image.addEventListener('load', measure, {
once: true
});
}