Skip to content

Instantly share code, notes, and snippets.

@manabuyasuda
Last active June 14, 2022 01:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manabuyasuda/abafa293aefba691ec2872896b2bdd8a to your computer and use it in GitHub Desktop.
Save manabuyasuda/abafa293aefba691ec2872896b2bdd8a to your computer and use it in GitHub Desktop.
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