Files
vant/packages/vant/src/back-top/BackTop.tsx
T
ZhousgandGitHub 08f82392b7 fix(BackTop): When teleport is the default, the parent scroll contain… (#11858)
* fix(BackTop): When teleport is the default, the parent scroll container is always window

* fix(BackTop): When teleport is the default, the parent scroll container is always window
2023-05-20 21:09:34 +08:00

148 lines
3.2 KiB
TypeScript

import {
ref,
watch,
computed,
Teleport,
nextTick,
onMounted,
defineComponent,
type PropType,
type TeleportProps,
type ExtractPropTypes,
} from 'vue';
// Utils
import {
extend,
addUnit,
inBrowser,
numericProp,
getScrollTop,
getZIndexStyle,
createNamespace,
makeNumericProp,
} from '../utils';
import { throttle } from '../lazyload/vue-lazyload/util';
// Composables
import { useEventListener, getScrollParent } from '@vant/use';
// Components
import { Icon } from '../icon';
const [name, bem] = createNamespace('back-top');
export const backTopProps = {
right: numericProp,
bottom: numericProp,
zIndex: numericProp,
target: [String, Object] as PropType<TeleportProps['to']>,
offset: makeNumericProp(200),
immediate: Boolean,
teleport: {
type: [String, Object] as PropType<TeleportProps['to']>,
default: 'body',
},
};
export type BackTopProps = ExtractPropTypes<typeof backTopProps>;
export default defineComponent({
name,
inheritAttrs: false,
props: backTopProps,
emits: ['click'],
setup(props, { emit, slots, attrs }) {
const show = ref(false);
const root = ref<HTMLElement>();
const scrollParent = ref<Window | Element>();
const style = computed(() =>
extend(getZIndexStyle(props.zIndex), {
right: addUnit(props.right),
bottom: addUnit(props.bottom),
})
);
const onClick = (event: MouseEvent) => {
emit('click', event);
scrollParent.value?.scrollTo({
top: 0,
behavior: props.immediate ? 'auto' : 'smooth',
});
};
const scroll = () => {
show.value = scrollParent.value
? getScrollTop(scrollParent.value) >= +props.offset
: false;
};
const getTarget = () => {
const { target } = props;
if (typeof target === 'string') {
const el = document.querySelector(target);
if (el) {
return el;
}
if (process.env.NODE_ENV !== 'production') {
console.error(
`[Vant] BackTop: target element "${target}" was not found, the BackTop component will not be rendered.`
);
}
} else {
return target as Element;
}
};
const updateTarget = () => {
if (inBrowser) {
nextTick(() => {
scrollParent.value = props.target
? getTarget()
: getScrollParent(root.value!);
scroll();
});
}
};
useEventListener('scroll', throttle(scroll, 100), { target: scrollParent });
onMounted(updateTarget);
watch(() => props.target, updateTarget);
return () => {
const Content = (
<div
ref={!props.teleport ? root : undefined}
class={bem({ active: show.value })}
style={style.value}
onClick={onClick}
{...attrs}
>
{slots.default ? (
slots.default()
) : (
<Icon name="back-top" class={bem('icon')} />
)}
</div>
);
if (props.teleport) {
return [
<div ref={root} class={bem('placeholder')}></div>,
<Teleport to={props.teleport}>{Content}</Teleport>,
];
}
return Content;
};
},
});