diff --git a/components/_util/getScroll.js b/components/_util/getScroll.js deleted file mode 100644 index 1b085e3e3..000000000 --- a/components/_util/getScroll.js +++ /dev/null @@ -1,17 +0,0 @@ -export default function getScroll(target, top) { - if (typeof window === 'undefined') { - return 0; - } - - const prop = top ? 'pageYOffset' : 'pageXOffset'; - const method = top ? 'scrollTop' : 'scrollLeft'; - const isWindow = target === window; - - let ret = isWindow ? target[prop] : target[method]; - // ie6,7,8 standard mode - if (isWindow && typeof ret !== 'number') { - ret = window.document.documentElement[method]; - } - - return ret; -} diff --git a/components/_util/getScroll.ts b/components/_util/getScroll.ts new file mode 100644 index 000000000..70b50141d --- /dev/null +++ b/components/_util/getScroll.ts @@ -0,0 +1,27 @@ +export function isWindow(obj: any) { + return obj !== null && obj !== undefined && obj === obj.window; +} + +export default function getScroll( + target: HTMLElement | Window | Document | null, + top: boolean, +): number { + if (typeof window === 'undefined') { + return 0; + } + const method = top ? 'scrollTop' : 'scrollLeft'; + let result = 0; + if (isWindow(target)) { + result = (target as Window)[top ? 'pageYOffset' : 'pageXOffset']; + } else if (target instanceof Document) { + result = target.documentElement[method]; + } else if (target) { + result = (target as HTMLElement)[method]; + } + if (target && !isWindow(target) && typeof result !== 'number') { + result = ((target as HTMLElement).ownerDocument || (target as Document)).documentElement?.[ + method + ]; + } + return result; +} diff --git a/components/_util/scrollTo.ts b/components/_util/scrollTo.ts index 31237d248..f41c1b649 100644 --- a/components/_util/scrollTo.ts +++ b/components/_util/scrollTo.ts @@ -1,9 +1,10 @@ -import getScroll from './getScroll'; +import raf from './raf'; +import getScroll, { isWindow } from './getScroll'; import { easeInOutCubic } from './easings'; interface ScrollToOptions { /** Scroll container, default as window */ - getContainer?: () => HTMLElement | Window; + getContainer?: () => HTMLElement | Window | Document; /** Scroll end callback */ callback?: () => any; /** Animation duration, default as 450 */ @@ -12,7 +13,6 @@ interface ScrollToOptions { export default function scrollTo(y: number, options: ScrollToOptions = {}) { const { getContainer = () => window, callback, duration = 450 } = options; - const container = getContainer(); const scrollTop = getScroll(container, true); const startTime = Date.now(); @@ -21,16 +21,18 @@ export default function scrollTo(y: number, options: ScrollToOptions = {}) { const timestamp = Date.now(); const time = timestamp - startTime; const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration); - if (container === window) { - window.scrollTo(window.pageXOffset, nextScrollTop); + if (isWindow(container)) { + (container as Window).scrollTo(window.pageXOffset, nextScrollTop); + } else if (container instanceof HTMLDocument || container.constructor.name === 'HTMLDocument') { + (container as HTMLDocument).documentElement.scrollTop = nextScrollTop; } else { (container as HTMLElement).scrollTop = nextScrollTop; } if (time < duration) { - requestAnimationFrame(frameFunc); + raf(frameFunc); } else if (typeof callback === 'function') { callback(); } }; - requestAnimationFrame(frameFunc); + raf(frameFunc); } diff --git a/components/back-top/index.tsx b/components/back-top/index.tsx index 2b18ad93b..c1e87948a 100644 --- a/components/back-top/index.tsx +++ b/components/back-top/index.tsx @@ -7,8 +7,13 @@ import { onBeforeUnmount, onMounted, reactive, + PropType, + ref, + watch, + onDeactivated, + computed, } from 'vue'; -import classNames from '../_util/classNames'; +import VerticalAlignTopOutlined from '@ant-design/icons-vue/VerticalAlignTopOutlined'; import PropTypes from '../_util/vue-types'; import addEventListener from '../vc-util/Dom/addEventListener'; import getScroll from '../_util/getScroll'; @@ -16,18 +21,12 @@ import { getTransitionProps, Transition } from '../_util/transition'; import { defaultConfigProvider } from '../config-provider'; import scrollTo from '../_util/scrollTo'; import { withInstall } from '../_util/type'; - -function getDefaultTarget() { - return window; -} +import throttleByAnimationFrame from '../_util/throttleByAnimationFrame'; export const backTopProps = { - // 滚动高度达到此参数值才出现 BackTop visibilityHeight: PropTypes.number.def(400), - // 回到顶部所需时间(ms) @4.4.0 duration: PropTypes.number.def(450), - // 设置需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 - target: PropTypes.func, + target: Function as PropType<() => HTMLElement | Window | Document>, prefixCls: PropTypes.string, onClick: PropTypes.func, // visible: PropTypes.looseBool, // Only for test. Don't use it. @@ -42,12 +41,15 @@ const BackTop = defineComponent({ emits: ['click'], setup(props, { slots, attrs, emit }) { const configProvider = inject('configProvider', defaultConfigProvider); - + const domRef = ref(); const state = reactive({ visible: false, scrollEvent: null, }); + const getDefaultTarget = () => + domRef.value && domRef.value.ownerDocument ? domRef.value.ownerDocument : window; + const scrollToTop = (e: Event) => { const { target = getDefaultTarget, duration } = props; scrollTo(0, { @@ -57,51 +59,85 @@ const BackTop = defineComponent({ emit('click', e); }; - const handleScroll = () => { - const { visibilityHeight, target = getDefaultTarget } = props; - const scrollTop = getScroll(target(), true); + const handleScroll = throttleByAnimationFrame((e: Event | { target: any }) => { + const { visibilityHeight } = props; + const scrollTop = getScroll(e.target, true); state.visible = scrollTop > visibilityHeight; + }); + + const bindScrollEvent = () => { + const { target } = props; + const getTarget = target || getDefaultTarget; + const container = getTarget(); + state.scrollEvent = addEventListener(container, 'scroll', (e: Event) => { + handleScroll(e); + }); + handleScroll({ + target: container, + }); }; + const scrollRemove = () => { + if (state.scrollEvent) { + state.scrollEvent.remove(); + } + (handleScroll as any).cancel(); + }; + + watch( + () => props.target, + () => { + scrollRemove(); + nextTick(() => { + bindScrollEvent(); + }); + }, + ); + onMounted(() => { nextTick(() => { - const getTarget = props.target || getDefaultTarget; - state.scrollEvent = addEventListener(getTarget(), 'scroll', handleScroll); - handleScroll(); + bindScrollEvent(); }); }); onActivated(() => { nextTick(() => { - handleScroll(); + bindScrollEvent(); }); }); - onBeforeUnmount(() => { - if (state.scrollEvent) { - state.scrollEvent.remove(); - } + onDeactivated(() => { + scrollRemove(); }); - return () => { - const { prefixCls: customizePrefixCls } = props; + onBeforeUnmount(() => { + scrollRemove(); + }); - const getPrefixCls = configProvider.getPrefixCls; - const prefixCls = getPrefixCls('back-top', customizePrefixCls); - const classString = classNames(prefixCls, attrs.class); + const prefixCls = computed(() => configProvider.getPrefixCls('back-top', props.prefixCls)); + + return () => { const defaultElement = ( -
-
+
+
+ +
); const divProps = { ...attrs, onClick: scrollToTop, - class: classString, + class: { + [`${prefixCls.value}`]: true, + [`${attrs.class}`]: attrs.class, + [`${prefixCls}-rtl`]: configProvider.direction === 'rtl', + }, }; const backTopBtn = state.visible ? ( -
{slots.default?.() || defaultElement}
+
+ {slots.default?.() || defaultElement} +
) : null; const transitionProps = getTransitionProps('fade'); return {backTopBtn}; diff --git a/components/back-top/style/index.less b/components/back-top/style/index.less index 73f3e3a95..60a3da575 100644 --- a/components/back-top/style/index.less +++ b/components/back-top/style/index.less @@ -18,6 +18,12 @@ display: none; } + &-rtl { + right: auto; + left: 100px; + direction: rtl; + } + &-content { width: 40px; height: 40px; @@ -26,20 +32,17 @@ text-align: center; background-color: @back-top-bg; border-radius: 20px; - transition: all 0.3s @ease-in-out; + transition: all 0.3s; &:hover { background-color: @back-top-hover-bg; - transition: all 0.3s @ease-in-out; + transition: all 0.3s; } } &-icon { - width: 14px; - height: 16px; - margin: 12px auto; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) - ~'100%/100%' no-repeat; + font-size: 24px; + line-height: 40px; } } diff --git a/v2-doc b/v2-doc index 4f1ece507..d19705328 160000 --- a/v2-doc +++ b/v2-doc @@ -1 +1 @@ -Subproject commit 4f1ece5073f736e79c6eb22527a9a83e8c6182b3 +Subproject commit d197053285b81e77718621c0b5b94cb3b21831a2