Last active
June 14, 2022 01:15
-
-
Save manabuyasuda/abafa293aefba691ec2872896b2bdd8a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { debounce } from '@utility/debounce' | |
import { throttle } from '@utility/throttle' | |
/** | |
* @classdesc 指定した要素までスクロールしたかを検知してコールバック関数で処理を実行します。 | |
* @author Manabu Yasuda <info@manabuyasuda.com> | |
* @example | |
* import ScrollFixed from '@lib/ScrollFixed' | |
* | |
* const fixed = new ScrollFixed({ | |
* root: '#root', | |
* activeClass: '-fixed', | |
* active: () => {}, | |
* inactive: () => {}, | |
* onLoad: false, | |
* onResize: false, | |
* onRemove: () => {}, | |
* }) | |
* fixed.init().run() | |
* | |
* MediaQuery.matches('lg', matches => { | |
* if (matches) { | |
* fixed.destroy() | |
* } else { | |
* fixed.run() | |
* } | |
* }); | |
*/ | |
export default class ScrollFixed { | |
/** | |
* @param {object} options | |
* @param {string} options.rootId ['root'] 固定する要素のid属性値を指定します。 | |
* @param {string} options.activeClass ['-fixed'] 固定された時に付与するクラス名を指定します。 | |
* @param {boolean|function} options.active [false] 固定時に実行されるコールバック関数です。 | |
* @param {boolean|function} options.inactive [false] 固定解除時に実行されるコールバック関数です。 | |
* @param {boolean|function} options.onRemove [false] リスナー削除時に実行されるコールバック関数です。 | |
* @param {boolean|function} options.onLoad [false] `load`イベント時に実行されるコールバック関数です。 | |
* @param {boolean|function} options.onResize [false] `resize`イベント時に実行されるコールバック関数です。 | |
*/ | |
constructor(options) { | |
const defaultOptions = { | |
rootId: 'root', | |
activeClass: '-fixed', | |
active: false, | |
inactive: false, | |
onRemove: false, | |
onLoad: false, | |
onResize: false, | |
} | |
this.options = Object.assign(defaultOptions, options) | |
Object.keys(this.options).forEach(key => { | |
this[key] = this.options[key] | |
}) | |
this.body = document.body | |
this.rootHeight = 0 | |
this.scrollAmount = 0 | |
this.isLoaded = false | |
this.isDestroy = false | |
this.isActive = false | |
this.selector = { | |
root: document.getElementById(this.rootId), | |
} | |
this.handleLoad = this.load.bind(this) | |
this.resize = this.resize.bind(this) | |
this.handleResize = debounce(this.resize) | |
this.scroll = this.scroll.bind(this) | |
this.handleScroll = throttle(this.scroll) | |
} | |
init() { | |
window.addEventListener('DOMContentLoaded', this.handleLoad) | |
this.isLoaded = true | |
return this | |
} | |
run() { | |
this.isDestroy = false | |
window.addEventListener('resize', this.handleResize) | |
window.addEventListener('scroll', this.handleScroll) | |
} | |
destroy() { | |
this.body.style.setProperty('padding-top', '') | |
this.selector.root.classList.remove(this.activeClass) | |
this.isDestroy = true | |
window.removeEventListener('DOMContentLoaded', this.handleLoad) | |
window.removeEventListener('resize', this.handleResize) | |
window.removeEventListener('scroll', this.handleScroll) | |
if (this.onRemove) { | |
this.onRemove() | |
} | |
} | |
updateScrollAmount() { | |
this.scrollAmount = this.selector.root.getBoundingClientRect().top + window.scrollY | |
} | |
updateRootHeight() { | |
this.rootHeight = this.selector.root.getBoundingClientRect().height | |
} | |
load() { | |
if (this.onLoad) { | |
this.onLoad() | |
} | |
this.updateRootHeight() | |
this.updateScrollAmount() | |
} | |
scroll() { | |
const currentScroll = window.pageYOffset | |
if (!this.isLoaded) return | |
if (this.isDestroy) { | |
this.body.style.setProperty('padding-top', '') | |
this.selector.root.classList.remove(this.activeClass) | |
return | |
} | |
if (currentScroll >= this.scrollAmount) { | |
this.isActive = true | |
this.body.style.setProperty('padding-top', `${this.rootHeight / 16}rem`) | |
this.selector.root.classList.add(this.activeClass) | |
if (this.active) { | |
this.active() | |
} | |
return | |
} | |
this.isActive = false | |
this.body.style.setProperty('padding-top', '') | |
this.selector.root.classList.remove(this.activeClass) | |
if (this.inactive) { | |
this.inactive() | |
} | |
} | |
resize() { | |
if (this.onResize) { | |
this.onResize() | |
} | |
this.update() | |
} | |
update() { | |
// 本来の位置を取得するため、スタイルを一時的にリセットする | |
if (this.isActive && !this.isDestroy) { | |
this.body.style.setProperty('padding-top', '') | |
this.selector.root.style.setProperty('position', 'static') | |
} | |
this.updateRootHeight() | |
this.updateScrollAmount() | |
// リセットしたスタイルを戻す | |
if (this.isActive && !this.isDestroy) { | |
this.body.style.setProperty('padding-top', `${this.rootHeight / 16}rem`) | |
this.selector.root.style.setProperty('position', '') | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment