feat: add component sticky
parent
ecf375c1b2
commit
d630710c83
@ -0,0 +1,3 @@
|
|||||||
|
import ContentDetailWrap from './src/ContentDetailWrap.vue'
|
||||||
|
|
||||||
|
export { ContentDetailWrap }
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ElCard, ElButton } from 'element-plus'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { ref, onMounted, defineEmits } from 'vue'
|
||||||
|
import { Sticky } from '@/components/Sticky'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
|
const prefixCls = getPrefixCls('content-wrap')
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
title: propTypes.string.def(''),
|
||||||
|
message: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['back'])
|
||||||
|
const offset = ref(85)
|
||||||
|
const contentDetailWrap = ref()
|
||||||
|
onMounted(() => {
|
||||||
|
offset.value = contentDetailWrap.value.getBoundingClientRect().top
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="content-detail-wrap-container" ref="contentDetailWrap" id="contentDetailWrap">
|
||||||
|
<Sticky :offset="offset">
|
||||||
|
<div class="detail-wrap-header">
|
||||||
|
<div style="float: left">
|
||||||
|
<el-button style="float: left; margin: 10px 0px 0px 10px" @click="emit('back')">
|
||||||
|
<Icon icon="ep:arrow-left" class="mr-5px" />
|
||||||
|
{{ t('common.back') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="float: right">
|
||||||
|
<slot name="right"></slot>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<slot name="title">
|
||||||
|
<label class="detail-wrap-header-title">{{ title }}</label>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Sticky>
|
||||||
|
<div style="padding: var(--app-content-padding)">
|
||||||
|
<ElCard :class="[prefixCls, 'mb-20px']" shadow="never">
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</ElCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="less">
|
||||||
|
.content-detail-wrap-container {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.detail-wrap-header {
|
||||||
|
position: relative;
|
||||||
|
z-index: 999 !important;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
padding-right: 20px;
|
||||||
|
line-height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #d0d0d0;
|
||||||
|
transition: position 0.6s ease;
|
||||||
|
|
||||||
|
.detail-wrap-header-title {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import Sticky from './src/Sticky.vue'
|
||||||
|
|
||||||
|
export { Sticky }
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { ref, onMounted, onActivated, shallowRef } from 'vue'
|
||||||
|
import { useEventListener, useWindowSize } from '@vueuse/core'
|
||||||
|
const props = defineProps({
|
||||||
|
offset: propTypes.number.def(0),
|
||||||
|
zIndex: propTypes.number.def(999),
|
||||||
|
className: propTypes.string.def(''),
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
validator: function (value: string) {
|
||||||
|
return ['top', 'bottom'].indexOf(value) !== -1
|
||||||
|
},
|
||||||
|
default: 'top'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const active = ref(false)
|
||||||
|
const positionStyle = ref('' as any)
|
||||||
|
const width = ref(undefined as any)
|
||||||
|
const height = ref(undefined as any)
|
||||||
|
const isSticky = ref(false)
|
||||||
|
const refSticky = ref()
|
||||||
|
const scrollContainer = shallowRef()
|
||||||
|
const { height: windowHeight } = useWindowSize()
|
||||||
|
onMounted(() => {
|
||||||
|
height.value = refSticky.value.getBoundingClientRect().height
|
||||||
|
scrollContainer.value = getScrollContainer(refSticky.value, true)
|
||||||
|
useEventListener(scrollContainer, 'scroll', handleScroll)
|
||||||
|
useEventListener('resize', handleReize)
|
||||||
|
handleScroll()
|
||||||
|
})
|
||||||
|
onActivated(() => {
|
||||||
|
handleScroll()
|
||||||
|
})
|
||||||
|
|
||||||
|
const camelize = (str) => {
|
||||||
|
return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyle = (element, styleName) => {
|
||||||
|
let _a
|
||||||
|
let key = camelize(styleName)
|
||||||
|
if (key === 'float') key = 'cssFloat'
|
||||||
|
try {
|
||||||
|
const style = element.style[styleName]
|
||||||
|
if (style) return style
|
||||||
|
const computed = (_a = document.defaultView) == null ? void 0 : _a.getComputedStyle(element, '')
|
||||||
|
return computed ? computed[styleName] : ''
|
||||||
|
} catch (e) {
|
||||||
|
return element.style[styleName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isScroll = (el, isVertical) => {
|
||||||
|
const key = {
|
||||||
|
undefined: 'overflow',
|
||||||
|
true: 'overflow-y',
|
||||||
|
false: 'overflow-x'
|
||||||
|
}[String(isVertical)]
|
||||||
|
const overflow = getStyle(el, key)
|
||||||
|
return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))
|
||||||
|
}
|
||||||
|
const getScrollContainer = (el, isVertical) => {
|
||||||
|
let parent = el
|
||||||
|
while (parent) {
|
||||||
|
if ([window, document, document.documentElement].includes(parent)) return window
|
||||||
|
if (isScroll(parent, isVertical)) return parent
|
||||||
|
parent = parent.parentNode
|
||||||
|
}
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
width.value = refSticky.value.getBoundingClientRect().width
|
||||||
|
if (props.position === 'top') {
|
||||||
|
const offsetTop = refSticky.value.getBoundingClientRect().top
|
||||||
|
if (offsetTop < props.offset) {
|
||||||
|
sticky()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
} else {
|
||||||
|
const offsetBottom = refSticky.value.getBoundingClientRect().bottom
|
||||||
|
console.log(offsetBottom, props.offset)
|
||||||
|
if (offsetBottom > windowHeight.value - props.offset) {
|
||||||
|
sticky()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleReize = () => {
|
||||||
|
if (isSticky.value) {
|
||||||
|
width.value = refSticky.value.getBoundingClientRect().width + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sticky = () => {
|
||||||
|
if (active.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
positionStyle.value = 'fixed'
|
||||||
|
active.value = true
|
||||||
|
width.value = width.value + 'px'
|
||||||
|
isSticky.value = true
|
||||||
|
}
|
||||||
|
const reset = () => {
|
||||||
|
if (!active.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
positionStyle.value = ''
|
||||||
|
width.value = 'auto'
|
||||||
|
active.value = false
|
||||||
|
isSticky.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div :style="{ height: height + 'px', zIndex: zIndex }" ref="refSticky">
|
||||||
|
<div
|
||||||
|
:class="className"
|
||||||
|
:style="{
|
||||||
|
top: position === 'top' ? offset + 'px' : '',
|
||||||
|
bottom: position !== 'top' ? offset + 'px' : '',
|
||||||
|
zIndex: zIndex,
|
||||||
|
position: positionStyle,
|
||||||
|
width: width,
|
||||||
|
height: height + 'px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<div>sticky</div>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ContentWrap } from '@/components/ContentWrap'
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { Sticky } from '@/components/Sticky'
|
||||||
|
import { ElAffix } from 'element-plus'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ContentWrap :title="t('stickyDemo.sticky')">
|
||||||
|
<Sticky :offset="90">
|
||||||
|
<div style="padding: 10px; background-color: lightblue"> Sticky 距离顶部90px </div>
|
||||||
|
</Sticky>
|
||||||
|
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
|
||||||
|
<el-affix :offset="150">
|
||||||
|
<div style="padding: 10px; background-color: lightblue">Affix 距离顶部150px </div>
|
||||||
|
</el-affix>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
|
||||||
|
<el-affix :offset="150" position="bottom">
|
||||||
|
<div style="padding: 10px; background-color: lightblue">Affix 距离底部150px </div>
|
||||||
|
</el-affix>
|
||||||
|
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
|
||||||
|
<Sticky :offset="90" position="bottom">
|
||||||
|
<div style="padding: 10px; background-color: lightblue"> Sticky 距离底部90px </div>
|
||||||
|
</Sticky>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
<p style="margin: 80px">Content</p>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
Loading…
Reference in New Issue