mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-21 01:02:59 +08:00
refactor(components): [affix] use vueuse utils (#6295)
* refactor(components): refactor affix * refactor: resolve review comments
This commit is contained in:
parent
b97ac7cfb8
commit
bb160c5f9b
@ -55,7 +55,7 @@ describe('Affix.vue', () => {
|
||||
})
|
||||
|
||||
test('should render offset props', async () => {
|
||||
const wrapper = _mount(() => <Affix offset={30}>AXIOM</Affix>)
|
||||
const wrapper = _mount(() => <Affix offset={30}>{AXIOM}</Affix>)
|
||||
await nextTick()
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
@ -85,7 +85,7 @@ describe('Affix.vue', () => {
|
||||
test('should render position props', async () => {
|
||||
const wrapper = _mount(() => (
|
||||
<Affix position="bottom" offset={20}>
|
||||
AXIOM
|
||||
{AXIOM}
|
||||
</Affix>
|
||||
))
|
||||
await nextTick()
|
||||
@ -119,7 +119,7 @@ describe('Affix.vue', () => {
|
||||
const wrapper = _mount(() => (
|
||||
<>
|
||||
<div class="target" style="height: 200px">
|
||||
<Affix target=".target">AXIOM</Affix>
|
||||
<Affix target=".target">{AXIOM}</Affix>
|
||||
</div>
|
||||
<div style="height: 1000px"></div>
|
||||
</>
|
||||
@ -163,7 +163,7 @@ describe('Affix.vue', () => {
|
||||
})
|
||||
|
||||
test('should render z-index props', async () => {
|
||||
const wrapper = _mount(() => <Affix z-index={1000}>AXIOM</Affix>)
|
||||
const wrapper = _mount(() => <Affix zIndex={1000}>{AXIOM}</Affix>)
|
||||
await nextTick()
|
||||
const mockAffixRect = jest
|
||||
.spyOn(wrapper.find('.el-affix').element, 'getBoundingClientRect')
|
||||
|
@ -1,19 +1,24 @@
|
||||
<template>
|
||||
<div ref="root" :class="ns.b()" :style="rootStyle">
|
||||
<div :class="{ [ns.m('fixed')]: fixed }" :style="affixStyle">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { useEventListener, useResizeObserver } from '@vueuse/core'
|
||||
import { getScrollContainer } from '@element-plus/utils'
|
||||
import { computed, onMounted, ref, shallowRef, watch, watchEffect } from 'vue'
|
||||
import {
|
||||
useEventListener,
|
||||
useElementBounding,
|
||||
useWindowSize,
|
||||
} from '@vueuse/core'
|
||||
import { getScrollContainer, throwError } from '@element-plus/utils'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { affixEmits, affixProps } from './affix'
|
||||
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
const COMPONENT_NAME = 'ElAffix'
|
||||
defineOptions({
|
||||
name: 'ElAffix',
|
||||
})
|
||||
@ -26,101 +31,97 @@ const ns = useNamespace('affix')
|
||||
const target = shallowRef<HTMLElement>()
|
||||
const root = shallowRef<HTMLDivElement>()
|
||||
const scrollContainer = shallowRef<HTMLElement | Window>()
|
||||
const { height: windowHeight } = useWindowSize()
|
||||
const {
|
||||
height: rootHeight,
|
||||
width: rootWidth,
|
||||
top: rootTop,
|
||||
bottom: rootBottom,
|
||||
update: updateRoot,
|
||||
} = useElementBounding(root)
|
||||
const targetRect = useElementBounding(target)
|
||||
|
||||
const fixed = ref(false)
|
||||
const height = ref(0) // height of root
|
||||
const width = ref(0) // width of root
|
||||
const scrollTop = ref(0) // scrollTop of documentElement
|
||||
const clientHeight = ref(0) // clientHeight of documentElement
|
||||
const scrollTop = ref(0)
|
||||
const transform = ref(0)
|
||||
|
||||
const rootStyle = computed<CSSProperties>(() => {
|
||||
return {
|
||||
height: fixed.value ? `${height.value}px` : '',
|
||||
width: fixed.value ? `${width.value}px` : '',
|
||||
height: fixed.value ? `${rootHeight.value}px` : '',
|
||||
width: fixed.value ? `${rootWidth.value}px` : '',
|
||||
}
|
||||
})
|
||||
|
||||
const affixStyle = computed<CSSProperties | undefined>(() => {
|
||||
if (!fixed.value) return
|
||||
const affixStyle = computed<CSSProperties>(() => {
|
||||
if (!fixed.value) return {}
|
||||
|
||||
const offset = props.offset ? `${props.offset}px` : 0
|
||||
const _transform = transform.value ? `translateY(${transform.value}px)` : ''
|
||||
|
||||
return {
|
||||
height: `${height.value}px`,
|
||||
width: `${width.value}px`,
|
||||
height: `${rootHeight.value}px`,
|
||||
width: `${rootWidth.value}px`,
|
||||
top: props.position === 'top' ? offset : '',
|
||||
bottom: props.position === 'bottom' ? offset : '',
|
||||
transform: _transform,
|
||||
transform: transform.value ? `translateY(${transform.value}px)` : '',
|
||||
zIndex: props.zIndex,
|
||||
}
|
||||
})
|
||||
|
||||
const update = () => {
|
||||
if (!root.value || !target.value || !scrollContainer.value) return
|
||||
if (!scrollContainer.value) return
|
||||
|
||||
const rootRect = root.value.getBoundingClientRect()
|
||||
const targetRect = target.value.getBoundingClientRect()
|
||||
height.value = rootRect.height
|
||||
width.value = rootRect.width
|
||||
scrollTop.value =
|
||||
scrollContainer.value instanceof Window
|
||||
? document.documentElement.scrollTop
|
||||
: scrollContainer.value.scrollTop || 0
|
||||
clientHeight.value = document.documentElement.clientHeight
|
||||
|
||||
if (props.position === 'top') {
|
||||
if (props.target) {
|
||||
const difference = targetRect.bottom - props.offset - height.value
|
||||
fixed.value = props.offset > rootRect.top && targetRect.bottom > 0
|
||||
const difference =
|
||||
targetRect.bottom.value - props.offset - rootHeight.value
|
||||
fixed.value = props.offset > rootTop.value && targetRect.bottom.value > 0
|
||||
transform.value = difference < 0 ? difference : 0
|
||||
} else {
|
||||
fixed.value = props.offset > rootRect.top
|
||||
fixed.value = props.offset > rootTop.value
|
||||
}
|
||||
} else if (props.target) {
|
||||
const difference =
|
||||
windowHeight.value -
|
||||
targetRect.top.value -
|
||||
props.offset -
|
||||
rootHeight.value
|
||||
fixed.value =
|
||||
windowHeight.value - props.offset < rootBottom.value &&
|
||||
windowHeight.value > targetRect.top.value
|
||||
transform.value = difference < 0 ? -difference : 0
|
||||
} else {
|
||||
if (props.target) {
|
||||
const difference =
|
||||
clientHeight.value - targetRect.top - props.offset - height.value
|
||||
fixed.value =
|
||||
clientHeight.value - props.offset < rootRect.bottom &&
|
||||
clientHeight.value > targetRect.top
|
||||
transform.value = difference < 0 ? -difference : 0
|
||||
} else {
|
||||
fixed.value = clientHeight.value - props.offset < rootRect.bottom
|
||||
}
|
||||
fixed.value = windowHeight.value - props.offset < rootBottom.value
|
||||
}
|
||||
}
|
||||
|
||||
const onScroll = () => {
|
||||
update()
|
||||
|
||||
const handleScroll = () => {
|
||||
emit('scroll', {
|
||||
scrollTop: scrollTop.value,
|
||||
fixed: fixed.value,
|
||||
})
|
||||
}
|
||||
|
||||
watch(fixed, (fixed) => {
|
||||
emit('change', fixed)
|
||||
})
|
||||
watch(fixed, (val) => emit('change', val))
|
||||
|
||||
onMounted(() => {
|
||||
if (props.target) {
|
||||
target.value =
|
||||
document.querySelector<HTMLElement>(props.target) ?? undefined
|
||||
if (!target.value) {
|
||||
throw new Error(`Target is not existed: ${props.target}`)
|
||||
}
|
||||
if (!target.value)
|
||||
throwError(COMPONENT_NAME, `Target is not existed: ${props.target}`)
|
||||
} else {
|
||||
target.value = document.documentElement
|
||||
}
|
||||
scrollContainer.value = getScrollContainer(root.value!, true)
|
||||
updateRoot()
|
||||
})
|
||||
|
||||
useEventListener(scrollContainer, 'scroll', onScroll)
|
||||
useResizeObserver(root, () => update())
|
||||
useResizeObserver(target, () => update())
|
||||
useEventListener(scrollContainer, 'scroll', handleScroll)
|
||||
watchEffect(update)
|
||||
|
||||
defineExpose({
|
||||
/** @description update affix status */
|
||||
|
Loading…
Reference in New Issue
Block a user