84 lines
3.6 KiB
JavaScript
84 lines
3.6 KiB
JavaScript
|
// ==UserScript==
|
||
|
// @name Adds scrolling JS that can be used within QB to do smarter scrolling
|
||
|
// @qute-js-world jseval
|
||
|
// @run-at document-start
|
||
|
// ==/UserScript==
|
||
|
unsafeWindow.scrollHelper = (() => {
|
||
|
const scrollableElemOverflowTypes = [
|
||
|
'auto',
|
||
|
'scroll',
|
||
|
]
|
||
|
|
||
|
const getFocusedWindow = (nextElem) => {
|
||
|
if (nextElem === null) return null
|
||
|
if (nextElem === undefined) return getFocusedWindow(window.document.activeElement ?? null)
|
||
|
return getFocusedWindow(nextElem.contentDocument?.activeElement ?? null) ?? nextElem.ownerDocument?.defaultView ?? null
|
||
|
}
|
||
|
|
||
|
const getScrollMaxY = ({ document: { documentElement } }) => documentElement.scrollHeight - documentElement.clientHeight
|
||
|
|
||
|
const getWindowVisibleArea = ({ document: { documentElement } }) => documentElement.clientHeight * documentElement.clientWidth
|
||
|
|
||
|
const findVertScrollableWindow = () => {
|
||
|
const focusedWindow = getFocusedWindow() ?? window
|
||
|
if (getScrollMaxY(focusedWindow) > 0) return focusedWindow
|
||
|
if (getScrollMaxY(window) > 0) return window
|
||
|
|
||
|
return Array
|
||
|
.from(window.frames)
|
||
|
.sort((x, y) => getWindowVisibleArea(y) - getWindowVisibleArea(x))
|
||
|
.find((frame) => getScrollMaxY(frame) > 0) ?? window
|
||
|
}
|
||
|
|
||
|
const getScrollTopMax = (elem) => elem.scrollHeight - elem.clientHeight
|
||
|
const isElementVertScrollable = (element) => element.clientHeight !==0 &&
|
||
|
scrollableElemOverflowTypes.includes(getComputedStyle(element).overflowY)
|
||
|
|
||
|
const findVertScrollableAncestor = (delta, nextElem) => {
|
||
|
if (!(nextElem?.parentNode instanceof Element)) return nextElem
|
||
|
|
||
|
if (isElementVertScrollable(nextElem)) {
|
||
|
if (delta < 0 && nextElem.scrollTop > 0) return nextElem
|
||
|
if (delta > 0 && nextElem.scrollTop < getScrollTopMax(nextElem)) return nextElem
|
||
|
if (delta === 0 && getScrollTopMax(nextElem) > 0) return nextElem
|
||
|
}
|
||
|
|
||
|
return findVertScrollableAncestor(delta, nextElem.parentNode)
|
||
|
}
|
||
|
|
||
|
const getSelectionElem = () => {
|
||
|
const selection = getFocusedWindow().getSelection()
|
||
|
return selection.rangeCount !== 0
|
||
|
? selection.getRangeAt(0).startContainer
|
||
|
: null
|
||
|
}
|
||
|
|
||
|
const getParentIfNotElement = (maybeElement) => maybeElement instanceof Element ? maybeElement : maybeElement?.parentNode
|
||
|
|
||
|
const findVertScrollable = (delta = 0) => {
|
||
|
const selectionScrollableElem = findVertScrollableAncestor(delta, getParentIfNotElement(getSelectionElem()))
|
||
|
if (selectionScrollableElem instanceof Element) return selectionScrollableElem
|
||
|
|
||
|
const scrollableDoc = findVertScrollableWindow().document
|
||
|
const scrollableElem = scrollableDoc.body || scrollableDoc.getElementsByTagName('body')[0] || scrollableDoc.documentElement
|
||
|
return findVertScrollableAncestor(delta, getParentIfNotElement(scrollableElem))
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
scrollTo: (position) => findVertScrollable().scrollTo({top: position}),
|
||
|
scrollToPercent: (percentPosition) => {
|
||
|
const scrollElement = findVertScrollable()
|
||
|
const paneHeight = scrollElement.scrollHeight
|
||
|
scrollElement.scrollTo({top: percentPosition / 100 * paneHeight})
|
||
|
},
|
||
|
scrollBy: (delta) => findVertScrollable(delta).scrollBy({top: delta, behavior: 'smooth'}),
|
||
|
scrollPage: (pages) => {
|
||
|
const fakeDelta = pages < 0 ? -10 : 10
|
||
|
const scrollElement = findVertScrollable(fakeDelta)
|
||
|
const pageHeight = scrollElement.clientHeight
|
||
|
scrollElement.scrollBy({top: pageHeight * pages})
|
||
|
},
|
||
|
}
|
||
|
|
||
|
})()
|