UNPKG

@livelybone/scroll-get

Version:

Some useful tool of browser scroll, such as tool for calculating position relative to page/client, tool for getting the native scrollbar width...

424 lines (348 loc) 12.3 kB
/** * Bundle of @livelybone/scroll-get * Generated: 2021-06-28 * Version: 6.2.1 * License: MIT * Author: 2631541504@qq.com */ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function getRect(el) { return el.getBoundingClientRect(); } function posRelativeToPage(el) { var o = { pageLeft: 0, pageTop: 0 }; var $el = el; while ($el) { o.pageLeft += $el.offsetLeft; o.pageTop += $el.offsetTop; $el = $el.offsetParent; } return o; } function posRelativeToClient(el) { var rect = getRect(el); return { clientLeft: rect.left, clientTop: rect.top }; } /** * @desc if el === window || el === undefined, return the global scrollbar width info * if el is an Element, return the scrollbar width info of the Element * * x: width of horizontal scrollbar * y: width of vertical scrollbar * */ function getNativeScrollbarWidth(el) { var $el = el || window; var isWindow = $el === window; try { var info = isWindow ? window.nativeScrollbarWidth : null; if (!(info && typeof info.y === 'number' && typeof info.x === 'number')) { // If nativeScrollbarWidth is illegal, reset it var doc = el && 'ownerDocument' in el ? el.ownerDocument : document; var wrapper = isWindow ? doc.createElement('div') : $el; if (isWindow) { wrapper.setAttribute('style', 'position:fixed;top:0;left:0;opacity:0;pointer-events:none;width:200px;height:200px;overflow:scroll'); doc.body.appendChild(wrapper); } info = { y: wrapper.offsetWidth - wrapper.clientWidth, x: wrapper.offsetHeight - wrapper.clientHeight }; if (isWindow) { window.nativeScrollbarWidth = info; doc.body.removeChild(wrapper); } } return info; } catch (e) { // For server render return { y: 17, x: 17 }; } } /** * This affects the performance of the animation by modifying the rate * * rate >= 0 && rate <= 1 * */ function defaultRateFactor(rate) { return rate + (1 - rate) * rate; } function animation(time, cb, rateFactor) { var $rateFactor = rateFactor || defaultRateFactor; var run = function run($cb) { window.requestAnimationFrame(function () { if ($cb()) run($cb); }); }; return new Promise(function (res) { var start = Date.now(); run(function () { var rate = $rateFactor(Math.min(1, (Date.now() - start) / time)); cb(rate); if (rate >= 1) { res(); return false; } return true; }); }); } /** * 获取元素的可能达到的最大的 scrollTop 和 scrollLeft 值 * * Gets the maximum possible scrollTop ans scrollLeft value for the element * */ function getMaxScrollOffset(el) { var style = window.getComputedStyle(el); var offset = { top: 0, left: 0 }; if (el.nodeName === 'HTML' || ['scroll', 'auto', 'overlay'].includes(style.overflowX)) { offset.left = Math.max(0, el.scrollWidth - el.clientWidth); } if (el.nodeName === 'HTML' || ['scroll', 'auto', 'overlay'].includes(style.overflowY)) { offset.top = Math.max(0, el.scrollHeight - el.clientHeight); } return offset; } function nonScrollOffset(offset) { return !offset.top && !offset.left; } /** * 向上遍历元素的祖先,获取第一个能滚动的祖先元素 * * Traverse up the ancestor of the element to get the first scrollable ancestor element * */ function getScrollParent($el) { if (!($el !== null && $el !== void 0 && $el.style)) return undefined; var style = window.getComputedStyle($el); if (style.position === 'fixed') return undefined; var doc = $el.ownerDocument; if (style.position === 'absolute') { if ($el.offsetParent) { return getScrollParent($el.offsetParent); } return !nonScrollOffset(getMaxScrollOffset(doc.body)) ? doc.body : getScrollParent(doc.body); } var scrollParent = $el.parentElement; if (scrollParent) { return !nonScrollOffset(getMaxScrollOffset(scrollParent)) || scrollParent === doc.documentElement ? scrollParent : getScrollParent(scrollParent); } return undefined; } /** * @param el The target element you want scroll to * @param [options] ScrollToElementOptions * */ function scrollToElement(el, options) { var _ref = options || {}, _ref$offset = _ref.offset, $offset = _ref$offset === void 0 ? 0 : _ref$offset, _ref$time = _ref.time, time = _ref$time === void 0 ? 300 : _ref$time, resOptions = _objectWithoutProperties(_ref, ["offset", "time"]); var offset = typeof $offset === 'number' ? { left: $offset, top: $offset } : { left: $offset.left || 0, top: $offset.top || 0 }; var scrollParent = getScrollParent(el); if (scrollParent && (!resOptions.topDisabled || !resOptions.leftDisabled)) { var parentScroll = function parentScroll() { return scrollToElement(scrollParent, _objectSpread2({ time: time }, resOptions)); }; var maxScrollOffset = getMaxScrollOffset(scrollParent); var originScrollOffset = { scrollLeft: scrollParent.scrollLeft, scrollTop: scrollParent.scrollTop }; var rect = getRect(el); var _scrollParentRect = getRect(scrollParent); if (scrollParent.nodeName === 'HTML') { _scrollParentRect.y = 0; } var delta = { left: Math.min(rect.left - _scrollParentRect.left + offset.left, maxScrollOffset.left - scrollParent.scrollLeft), top: Math.min(rect.top - _scrollParentRect.top + offset.top, maxScrollOffset.top - scrollParent.scrollTop) }; if (delta.left && !resOptions.leftDisabled || delta.top && !resOptions.topDisabled) { return animation(time, function (rate) { if (!resOptions.topDisabled) { scrollParent.scrollTop = originScrollOffset.scrollTop + delta.top * rate; } if (!resOptions.leftDisabled) { scrollParent.scrollLeft = originScrollOffset.scrollLeft + delta.left * rate; } }, resOptions.rateFactor).then(resOptions.affectParent ? parentScroll : null); } if (resOptions.affectParent) { return parentScroll(); } } return Promise.resolve(); } function getViewElementsWhenScroll(scrollElement, targetElements, cb) { if (targetElements.length > 0) { var oldEl = []; var scroll = function scroll(ev) { var scrollRect = getRect(scrollElement); var elementsRect = targetElements.map(getRect); // 重新计算元素当前的区域高度及可见区域高度 var rects = elementsRect.map(function (rect, i) { var $rect = rect; $rect.viewHeight = Math.max(0, Math.min($rect.top + $rect.height, scrollRect.top + scrollRect.height) - Math.max(scrollRect.top, $rect.top)); $rect.viewWidth = Math.max(0, Math.min($rect.left + $rect.width, scrollRect.left + scrollRect.width) - Math.max(scrollRect.left, $rect.left)); if ($rect.height && $rect.width) { $rect.viewPercent = $rect.viewHeight * $rect.viewWidth / $rect.height * $rect.width; } else if (!$rect.height && !$rect.width) { $rect.viewPercent = 0; } else if (!$rect.height) { $rect.viewPercent = $rect.viewWidth / $rect.width; } else { $rect.viewPercent = $rect.viewHeight / $rect.height; } return { rect: $rect, element: targetElements[i] }; }); // 通过比较各自当前的可见区域的大小获得当前的最近元素 var viewElements = rects.filter(function (el) { return el.rect.viewPercent > 0; }).sort(function (a, b) { var l1 = b.rect.viewPercent - a.rect.viewPercent; if (l1) return l1; return b.rect.viewHeight * b.rect.viewWidth - a.rect.viewHeight * a.rect.viewWidth; }); if (viewElements.length !== oldEl.length || viewElements.some(function (el, i) { return el.element !== oldEl[i].element; })) { cb(oldEl = viewElements, scrollRect, ev); } }; scroll(); scrollElement.addEventListener('scroll', scroll); return function () { return scrollElement.removeEventListener('scroll', scroll); }; } return function () {}; } /** * Judge whether the element is in current page view * */ function isElementInView(el) { if (!el) return false; var rect = el.getBoundingClientRect(); var x = rect.x + rect.width / 2; var y = rect.y + rect.height / 2; var points = [[x, rect.y], [x, rect.y + rect.height - 1], [rect.x, y], [rect.x + rect.width - 1, y], [x, y]]; return points.some(function (point) { var _el$ownerDocument; return el.contains((_el$ownerDocument = el.ownerDocument).elementFromPoint.apply(_el$ownerDocument, _toConsumableArray(point))); }); } export { animation, getMaxScrollOffset, getNativeScrollbarWidth, getRect, getScrollParent, getViewElementsWhenScroll, isElementInView, posRelativeToClient, posRelativeToPage, scrollToElement };