【更新日 : 】
【JavaScript】追従要素が段々積み重なっていく動作を実装する
- Category:
- css / js
- Tags:
- css, JavaScript, Vanilla JS, スクロール, 自作関数
追従要素が段々積み重なっていく動作をposition: sticky;とJSで実装したサンプルです。
position: sticky;とJSで実装
See the Pen position: sticky; Stacking elements within a specific area Vanilla JS by web_nak (@web_walking_nak) on CodePen.
JavaScript
//position: sticky;で指定エリア内に要素を追従させ積み重ねる関数
const areaStacking = () => {
//エリアチェック
const areas = document.querySelectorAll('.js-stacking__area');
if(areas.length === 0) {
return;
}
//追従チェック関数
const checkPosition = targets => {
//ブロックごとの高さを取得
const height = [];
targets.forEach(target => {
target.style.paddingTop = '';
target.style.paddingBottom = '';
height.push(target.offsetHeight);
});
const heightLen = height.length;
//高さを設定
targets.forEach((target, index) => {
//ブロックのtop位置設定
let topHeight = 0;
if(index === 1) {
topHeight = height[0];
} else if(index > 1) {
topHeight = height.slice(0,index);
topHeight = topHeight.reduce((a, x) => a + x);
}
target.style.paddingTop = topHeight + 'px';
//ブロックのbottom位置設定
let bottomHeight = 0;
if(index !== heightLen - 1) {
bottomHeight = height.slice(index+1,heightLen);
bottomHeight = bottomHeight.reduce((a, x) => a + x);
target.style.paddingBottom = bottomHeight + 'px';
}
});
}
areas.forEach(area => {
//エリア内に追従要素が存在する場合のみ処理する
const targets = area.querySelectorAll('.js-stacking__item');
if(targets.length > 0) {
checkPosition(targets);
/*
レスポンシブ対応
*/
//表示エリアリサイズ監視 ResizeObserver
const resizeObserver = new ResizeObserver(() => {
checkPosition(targets);
});
//リサイズ監視開始
resizeObserver.observe(area);
}
});
};
HTML
アニメーションするエリアに .js-stacking__area
を追加し、
アニメーションさせたい要素に.js-stacking__item
を追加します。
<div class="js-stacking__area">
<p class="js-stacking__item">テキスト1</p>
<p class="js-stacking__item">テキスト2</p>
</div>
CSS
.js-stacking__item {
position: sticky;
top: 4em;
}
関数の実行
任意の場所で以下を実行し関数を呼び出してください。
areaStacking();
position: fixed; とJSで実装
position: sticky;と同等の機能です。
overflow: hidden; との兼ね合いで stickyが利用できない時には利用できるかもしれません。
See the Pen Stacking elements within a specific area Vanilla JS by web_nak (@web_walking_nak) on CodePen.
JavaScript
//指定エリア内に要素を追従させ積み重ねる関数
const areaFixedText = () => {
//エリアチェック
const areas = document.querySelectorAll('.js-fixed-text__area');
if(areas.length === 0) {
return;
}
//追従チェック関数
const checkFixed = (targets,area) => {
//ブロックごとの高さを取得
const height = [];
targets.forEach(target => {
target.style.paddingTop = '';
target.style.paddingBottom = '';
height.push(target.offsetHeight);
});
const heightLen = height.length;
//追従エリアの情報を取得
const areaPosi = area.getBoundingClientRect().top;
const areaHeight = area.clientHeight;
//追従の終了ポイント
const endPosi = areaPosi + areaHeight;
//ブロックごとの処理
targets.forEach((target, index) => {
//ブロックのtop位置設定
let topHeight = 0;
if(index === 1) {
topHeight = height[0];
} else if(index > 1) {
topHeight = height.slice(0,index);
topHeight = topHeight.reduce((a, x) => a + x);
}
target.style.paddingTop = topHeight + 'px';
//ブロックのbottom位置設定
let bottomHeight = 0;
if(index !== heightLen - 1) {
bottomHeight = height.slice(index+1,heightLen);
bottomHeight = bottomHeight.reduce((a, x) => a + x);
target.style.paddingBottom = bottomHeight + 'px';
}
//要素の位置と高さを取得
let targetHeight = target.clientHeight;
const parent = target.parentNode;
const parentPosi = parent.getBoundingClientRect().top;
const startPosi = parent.getBoundingClientRect().top;
parent.style.height = '';
const parentHeight = parent.clientHeight;
if(targetHeight > parentHeight) {
parent.style.height = targetHeight + 'px';
}
//エリア内の処理
if(0 > startPosi && targetHeight < endPosi) {
target.classList.add('is-fixed');
target.style.top = '';
//エリアより上の処理
} else if(0 <= startPosi) {
target.classList.remove('is-fixed');
target.style.top = '';
//エリアより下の処理
} else {
target.classList.remove('is-fixed');
//停止位置を設定
topHeight = 0;
if(index < heightLen - 1) {
topHeight = height.slice(index + 1,heightLen);
topHeight = topHeight.reduce((a, x) => a + x);
}
target.style.top = (areaHeight - ((-areaPosi) - (-parentPosi)) - targetHeight + bottomHeight - topHeight) + 'px';
}
});
}
//エリアごとに処理
areas.forEach(area => {
//エリア内に追従要素が存在する場合のみ処理する
const targets = area.querySelectorAll('.js-fixed-text__item');
if(targets.length > 0) {
/*
要素が画面内に入ったら処理
*/
const listener = {
handleEvent: () => {
checkFixed(targets,area);
}
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if(entry.isIntersecting) {
//画面に表示されているエリアのみスクロールイベント発火
window.addEventListener('scroll', listener, {passive: true});
} else {
//画面外のときはスクロールイベント削除
window.removeEventListener('scroll', listener, {passive: true});
}
});
});
//observer監視開始
observer.observe(area);
/*
レスポンシブ対応
*/
//表示エリアリサイズ監視 ResizeObserver
const resizeObserver = new ResizeObserver(() => {
checkFixed(targets,area);
});
//リサイズ監視開始
resizeObserver.observe(area);
}
});
}
HTML
アニメーションするエリアに .js-fixed-text__area を追加します。
アニメーションさせたい要素は .js-fixed-text__item-area でグループ化します。
追従させるテキストに .js-fixed-text__item を追加し、
高さ確保用の .js-fixed-text__hidden 要素を追加してください。
<div class="js-fixed-text__area">
<div class="js-fixed-text__item-area">
<p class="js-fixed-text__hidden" aria-hidden="true">テキスト1</p>
<p class="js-fixed-text__item">テキスト1</p>
</div>
<div class="js-fixed-text__item-area">
<p class="js-fixed-text__hidden" aria-hidden="true">テキスト2</p>
<p class="js-fixed-text__item">テキスト2</p>
</div>
</div>
CSS
.js-fixed-text__item-area {
position: relative;
}
.js-fixed-text__item {
position: absolute;
top: 0;
}
.js-fixed-text__item.is-fixed {
position: fixed;
}
.js-fixed-text__hidden {
opacity: 0;
pointer-events: none;
}
関数の実行
任意の場所で以下を実行し関数を呼び出してください。
areaFixedText();