diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-16 15:21:43 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-16 15:21:43 +0900 |
| commit | d56fc4186529bf41fe840cb3497f1a363ac84475 (patch) | |
| tree | e4da77fc7544fb8d5619e9799ee9ce3494ccd80b /packages/frontend/src/scripts/scroll.ts | |
| parent | masterブランチをmaster_securityとマージ (#9260) (diff) | |
| parent | 13.0.0 (diff) | |
| download | misskey-d56fc4186529bf41fe840cb3497f1a363ac84475.tar.gz misskey-d56fc4186529bf41fe840cb3497f1a363ac84475.tar.bz2 misskey-d56fc4186529bf41fe840cb3497f1a363ac84475.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/frontend/src/scripts/scroll.ts')
| -rw-r--r-- | packages/frontend/src/scripts/scroll.ts | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/packages/frontend/src/scripts/scroll.ts b/packages/frontend/src/scripts/scroll.ts new file mode 100644 index 0000000000..e3d9dc00c2 --- /dev/null +++ b/packages/frontend/src/scripts/scroll.ts @@ -0,0 +1,129 @@ +type ScrollBehavior = 'auto' | 'smooth' | 'instant'; + +export function getScrollContainer(el: HTMLElement | null): HTMLElement | null { + if (el == null || el.tagName === 'HTML') return null; + const overflow = window.getComputedStyle(el).getPropertyValue('overflow-y'); + if (overflow === 'scroll' || overflow === 'auto') { + return el; + } else { + return getScrollContainer(el.parentElement); + } +} + +export function getStickyTop(el: HTMLElement, container: HTMLElement | null = null, top: number = 0) { + if (!el.parentElement) return top; + const data = el.dataset.stickyContainerHeaderHeight; + const newTop = data ? Number(data) + top : top; + if (el === container) return newTop; + return getStickyTop(el.parentElement, container, newTop); +} + +export function getScrollPosition(el: HTMLElement | null): number { + const container = getScrollContainer(el); + return container == null ? window.scrollY : container.scrollTop; +} + +export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) { + // とりあえず評価してみる + if (isTopVisible(el)) { + cb(); + if (once) return null; + } + + const container = getScrollContainer(el) || window; + + const onScroll = ev => { + if (!document.body.contains(el)) return; + if (isTopVisible(el, tolerance)) { + cb(); + if (once) removeListener(); + } + }; + + function removeListener() { container.removeEventListener('scroll', onScroll); } + container.addEventListener('scroll', onScroll, { passive: true }); + return removeListener; +} + +export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance: number = 1, once: boolean = false) { + const container = getScrollContainer(el); + + // とりあえず評価してみる + if (isBottomVisible(el, tolerance, container)) { + cb(); + if (once) return null; + } + + const containerOrWindow = container || window; + const onScroll = ev => { + if (!document.body.contains(el)) return; + if (isBottomVisible(el, 1, container)) { + cb(); + if (once) removeListener(); + } + }; + + function removeListener() { + containerOrWindow.removeEventListener('scroll', onScroll); + } + containerOrWindow.addEventListener('scroll', onScroll, { passive: true }); + return removeListener; +} + +export function scroll(el: HTMLElement, options: ScrollToOptions | undefined) { + const container = getScrollContainer(el); + if (container == null) { + window.scroll(options); + } else { + container.scroll(options); + } +} + +/** + * Scroll to Top + * @param el Scroll container element + * @param options Scroll options + */ +export function scrollToTop(el: HTMLElement, options: { behavior?: ScrollBehavior; } = {}) { + scroll(el, { top: 0, ...options }); +} + +/** + * Scroll to Bottom + * @param el Content element + * @param options Scroll options + * @param container Scroll container element + */ +export function scrollToBottom( + el: HTMLElement, + options: ScrollToOptions = {}, + container = getScrollContainer(el), +) { + if (container) { + container.scroll({ top: el.scrollHeight - container.clientHeight + getStickyTop(el, container) || 0, ...options }); + } else { + window.scroll({ + top: (el.scrollHeight - window.innerHeight + getStickyTop(el, container) + (window.innerWidth <= 500 ? 96 : 0)) || 0, + ...options + }); + } +} + +export function isTopVisible(el: HTMLElement, tolerance: number = 1): boolean { + const scrollTop = getScrollPosition(el); + return scrollTop <= tolerance; +} + +export function isBottomVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) { + if (container) return el.scrollHeight <= container.clientHeight + Math.abs(container.scrollTop) + tolerance; + return el.scrollHeight <= window.innerHeight + window.scrollY + tolerance; +} + +// https://ja.javascript.info/size-and-scroll-window#ref-932 +export function getBodyScrollHeight() { + return Math.max( + document.body.scrollHeight, document.documentElement.scrollHeight, + document.body.offsetHeight, document.documentElement.offsetHeight, + document.body.clientHeight, document.documentElement.clientHeight + ); +} |