🍣
スクロールすると追従させたい要素を固定させるやつ
スクロールすると追従させたい要素を固定させるコードです
ターゲットがobserver領域内にいる時のみscrollイベントを発動させてます
可能な限り関数化し冗長な部分を減らしました
html
<div class="group">
<div class="group-contents --01b js-fixed-contents">
<p><img src="https://picsum.photos/id/1084/800/600" alt=""></p>
<p><img src="https://picsum.photos/id/200/600/500" alt=""></p>
</div>
<div class="group-aside js-fixed-area">
<div class="group-nav js-fixed-elm 02">
<h2>追従パーツ(JS)2</h2>
<p>指定エリアの中でrrrr<br>追従します。(JS)2</p>
</div>
</div>
</div>
js
'use strict'
const { log } = console;
/**
* 指定エリア内に要素を追従させる関数(追従要素,追従エリア)
* @param {*} fixedElm 追従させたい要素のクラス名
* @param {*} fixedArea エリアのクラス名
* @param {*} addOptions observer追加オプション
* @returns
*/
export function contentsFixed(fixedElm, fixedArea, addOptions) {
//エリアチェック
const areas = document.querySelectorAll(fixedArea);
if (areas.length === 0) {
return;
}
/**
* 追従要素への処理
* @param {*} target 追従要素
* @param {*} area エリア
*/
const targetProcess = (target, area) => {
//要素の位置と高さを取得
const startPosi = area.getBoundingClientRect().top;
const targetHeight = target.clientHeight;
const areaHeight = area.clientHeight;
const endPosi = startPosi + areaHeight;
//エリア内の処理
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');
//停止位置を設定
target.style.top = (areaHeight - targetHeight) + 'px';
}
}
/**
* 追従チェック
* @param {*} target 追従させたい要素
* @param {*} area エリア要素
*/
const checkFixed = (target, area) => {
targetProcess(target, area)
}
/**
* observer callback
* @param {*} entry observer領域と交差した対象
* @param {*} area エリア
*/
const callback = (entry, area) => {
//observer時の追従チェック
const checkFixedIntersect = () => {
targetProcess(entry.target, area);
}
//determine whether or not in the observer and excute addEvent or removeEvent
if (entry.isIntersecting) {
window.addEventListener('scroll', checkFixedIntersect);
} else {
window.removeEventListener('scroll', checkFixedIntersect);
}
}
//observer base options(2nd argument)
const options = {
root: null,
rootMargin: "0px",
threshold: 0,
once: false,
};
const mergeOptions = Object.assign(options, addOptions);
/**
* エリア毎に処理を実行
*/
areas.forEach(area => {
const target = area.querySelector(fixedElm);
//エリア内に追従要素が存在する場合のみ処理する
if (target) {
checkFixed(target, area);
//resize
window.addEventListener('resize', () => {
checkFixed(target, area);
});
//observer 1st argument
const obs = (entries) => {
entries.forEach(entry => {
//excute callback func to each entry
callback(entry, area);
});
}
new IntersectionObserver(obs, mergeOptions).observe(target);
}
});
}
クラス
'use strict'
const { log } = console;
export class ContentsFixed {
constructor(fixedElm, fixedArea, options) {
this.areas = document.querySelectorAll(fixedArea); //エリア
if (this.areas.length === 0) return;
this.fixedElm = fixedElm; //追従要素名
this.options = options; //observer基本オプション
this.mergeOptions; //observerオプション
this.startPosi; //エリアのブラウザ上端からの相対位置
this.targetHeight; //追従対象の高さ
this.areaHeight; //エリアの高さ
this.endPosi; //エリア下端の位置
this._areaProcess(); //エリア毎に処理
}
//追従チェック
_checkFixed(target, area) {
//処理
this._targetProcess(target, area);
}
/**
* observer callback
* @param {*} entry observer対象
* @param {*} area エリア
*/
_callback(entry, area) {
const checkFixedIntersect = () => {
//処理
this._targetProcess(entry.target, area);
}
if (entry.isIntersecting) {
window.addEventListener('scroll', checkFixedIntersect);
} else {
window.removeEventListener('scroll', checkFixedIntersect);
}
}
/**
* 追従チェックの処理内容
* @param {*} target 追従対象
* @param {*} area エリア
*/
_targetProcess(target, area) {
//要素の位置と高さを取得
this.startPosi = area.getBoundingClientRect().top;
this.targetHeight = target.clientHeight;
this.areaHeight = area.clientHeight;
this.endPosi = this.startPosi + this.areaHeight;
//エリア内の処理
if (0 > this.startPosi && this.targetHeight < this.endPosi) {
target.classList.add('is-fixed');
target.style.top = '';
//エリアより上の処理
} else if (0 <= this.startPosi) {
target.classList.remove('is-fixed');
target.style.top = '';
//エリアより下の処理
} else {
target.classList.remove('is-fixed');
//停止位置を設定
target.style.top = (this.areaHeight - this.targetHeight) + 'px';
}
}
/**
* merge observer options
*/
_options() {
const options = {
root: null,
rootMargin: "0px",
threshold: 0,
once: false,
};
this.mergeOptions = Object.assign(options, this.options);
}
/**
* エリア毎の処理
*/
_areaProcess() {
this.areas.forEach(area => {
const target = area.querySelector(this.fixedElm);
//エリア内に追従要素が存在する場合のみ処理する
if (target) {
this._checkFixed(target, area);
//resize
window.addEventListener('resize', () => {
this._checkFixed(target, area);
});
//observer 1st argument
const obs = (entries) => {
entries.forEach(entry => {
this._callback(entry, area);
});
}
new IntersectionObserver(obs, this.mergeOptions).observe(target);
}
});
}
}
Discussion