2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
import { withNoopInstall } from '../../utils'
import Sticky from './src/sticky.vue'
export const TnSticky = withNoopInstall(Sticky)
export default TnSticky
export * from './src/sticky'
export type { TnStickyInstance } from './src/instance'
@@ -0,0 +1,2 @@
export * from './sticky-custom'
export * from './use-sticky'
@@ -0,0 +1,73 @@
import { computed } from 'vue'
import { useNamespace } from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties, Ref } from 'vue'
import type { StickyProps } from '../sticky'
import type { StickyRectInfo } from '../types'
export const useStickyCustomStyle = (
props: StickyProps,
suppoerCSSSticky: Ref<boolean | null>,
stickyDistance: Ref<number>,
stickyStatus: Ref<boolean>,
stickyRectInfo: Ref<StickyRectInfo>
) => {
const ns = useNamespace('sticky')
// sticky样式
const stickyStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置ZIndex
if (props.zIndex) style.zIndex = props.zIndex
// 开启吸顶
if (props.enabled) {
// 是否支持css sticky
if (suppoerCSSSticky.value) {
style.position = 'sticky'
style.top = `${stickyDistance.value}px`
} else {
style.height = stickyStatus.value
? formatDomSizeValue(stickyRectInfo.value.height, 'px')
: 'auto'
}
} else {
// 不需要吸顶的情况下设置默认的position
// #ifdef APP-NVUE
style.position = 'relative'
// #endif
// #ifndef APP-NVUE
style.position = 'static'
// #endif
}
return style
})
// sticky内容样式
const stickyContentStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 不支持CSS Sticky,使用fixed布局实现
if (!suppoerCSSSticky.value) {
style.position = stickyStatus.value ? 'fixed' : 'static'
style.top = `${stickyDistance.value}px`
style.left = `${stickyRectInfo.value.left}px`
style.width = formatDomSizeValue(stickyRectInfo.value.width, 'px')
}
if (stickyDistance.value > 0) {
style.top = `${stickyDistance.value}px`
}
return style
})
return {
ns,
stickyStyle,
stickyContentStyle,
}
}
@@ -0,0 +1,100 @@
import { getCurrentInstance, ref } from 'vue'
import { debugWarn } from '../../../..//utils'
export const useStickySupport = () => {
const supportCSSSticky = ref<boolean | null>(null)
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnSticky', '请在 setup 中使用 useStickySupport')
}
// H5通过创建元素的方法嗅探是否支持 CSS Sticky
const checkCSSStickySupportForH5 = () => {
// #ifdef H5
const vendorList = ['', '-webkit-', '-ms-', '-moz-', '-o-'],
vendorListLength = vendorList.length,
stickyElement = document.createElement('div')
for (let i = 0; i < vendorListLength; i++) {
stickyElement.style.position = `${vendorList[i]}sticky`
if (stickyElement.style.position !== '') {
supportCSSSticky.value = true
return
}
}
supportCSSSticky.value = false
// #endif
}
// 在APP和微信小程序上,通过uni.createSelectorQuery嗅探是否支持 CSS Sticky
const checkCSSStickySupportForAPPAndMPWX = (selector: string) => {
// #ifdef APP-VUE || MP-WEIXIN
uni
.createSelectorQuery()
.in(instance)
.select(selector)
.fields(
{
computedStyle: ['position'],
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
() => {}
)
.exec((res) => {
if ('sticky' === res[0].position) {
supportCSSSticky.value = true
} else {
supportCSSSticky.value = false
}
})
// #endif
}
// 根据机型检测是否支持 CSS Sticky
const checkCSSStickySupportForDevice = () => {
// 获取手机对应的系统\操作系统名称和版本
const { platform, system } = uni.getSystemInfoSync()
const os = platform.toLowerCase()
const osVersion = system
if (
(os === 'android' && Number(osVersion.replace('Android', '')) > 8) ||
os === 'ios'
) {
supportCSSSticky.value = true
} else {
supportCSSSticky.value = false
}
}
// 检测当前环境是否支持 CSS Sticky
const checkCSSStickySupport = async (selector: string) => {
// 检测H5是否支持 CSS Sticky,主流浏览器都支持
// #ifdef H5
checkCSSStickySupportForH5()
// #endif
// 通过机型去判断是否支持 CSS Sticky
if (!supportCSSSticky.value) {
checkCSSStickySupportForDevice()
}
// 通过节点去判断是否支持 CSS Sticky
if (!supportCSSSticky.value) {
// #ifdef APP-VUE || MP-WEIXIN
checkCSSStickySupportForAPPAndMPWX(selector)
// #endif
}
// #ifdef APP-NVUE
// nvue默认时支持的
supportCSSSticky.value = true
// #endif
}
return {
supportCSSSticky,
checkCSSStickySupport,
}
}
@@ -0,0 +1,165 @@
import {
computed,
getCurrentInstance,
nextTick,
onMounted,
onUnmounted,
ref,
watch,
} from 'vue'
import { useObserver, useSelectorQuery } from '../../../../hooks'
import {
debugWarn,
generateId,
isEmptyVariableInDefault,
} from '../../../../utils'
import { useStickySupport } from './use-sticky-support'
import type { StickyProps } from '../sticky'
import type { StickyRectInfo } from '../types'
export const useSticky = (props: StickyProps) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnSticky', 'useSticky 必须在 setup 中使用')
}
const { emit } = instance!
// 组件id
const componentId = `ts-${generateId()}`
const { supportCSSSticky, checkCSSStickySupport } = useStickySupport()
const { connectObserver, disconnectObserver } = useObserver(instance)
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 吸顶的距离
const stickyDistance = computed<number>(() =>
isEmptyVariableInDefault(props?.offsetTop, 0)
)
// 是否吸顶
const stickyStatus = ref<boolean>(false)
// 设置吸顶的状态
const setStickyStatus = (status: boolean) => {
if (status) {
emit('change', true)
} else if (stickyStatus.value) {
emit('change', false)
}
stickyStatus.value = status
}
// 初始化节点次数
let initCount = 0
// sticky容器节点信息,防止在js模式下设置为fixed后元素塌陷无法进行交互
const stickyContainerRect = ref<StickyRectInfo>({
width: 'auto',
height: 'auto',
left: 0,
})
// 监听节点信息
const monitorNodeInfo = () => {
connectObserver(
`#${componentId}`,
(observerRes) => {
if (!props.enabled) return
setStickyStatus(
observerRes.boundingClientRect.top <= stickyDistance.value
)
},
{
type: 'relativeToViewport',
margins: {
top: -stickyDistance.value,
},
},
{
thresholds: [0.95, 0.98, 1],
}
)
}
// 初始化Observer
const initObserver = async () => {
disconnectObserver()
try {
const rectInfo = await getSelectorNodeInfo(`#${componentId}`)
initCount = 0
// 设置容器信息
stickyContainerRect.value.width = rectInfo.width || 0
stickyContainerRect.value.height = rectInfo.height || 0
stickyContainerRect.value.left = rectInfo.left || 0
nextTick(() => {
monitorNodeInfo()
})
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnSticky', `获取sticky节点信息失败: ${err}`)
return
}
initCount++
setTimeout(() => {
initObserver()
}, 150)
}
}
watch(
() => supportCSSSticky.value,
(val) => {
if (val === false && props.enabled) {
initObserver()
}
}
)
// 如果修改了吸顶高度则重新进行监听
watch(
() => props.offsetTop,
() => {
nextTick(() => {
initSticky()
})
}
)
// 监听是否动态设置吸顶状态
watch(
() => props.enabled,
(val) => {
if (!val) {
setStickyStatus(false)
disconnectObserver()
} else {
disconnectObserver()
monitorNodeInfo()
}
}
)
// 初始化组件
const initSticky = () => {
checkCSSStickySupport(`#${componentId}`)
}
onMounted(() => {
nextTick(() => {
initSticky()
})
})
onUnmounted(() => {
disconnectObserver()
})
return {
componentId,
supportCSSSticky,
stickyStatus,
stickyContainerRect,
stickyDistance,
}
}
@@ -0,0 +1,3 @@
import type Sticky from './sticky.vue'
export type TnStickyInstance = InstanceType<typeof Sticky>
+38
View File
@@ -0,0 +1,38 @@
import { ZIndex } from '../../../constants'
import { buildProps, isBoolean } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export const stickyProps = buildProps({
/**
* @description 是否开启粘性布局
*/
enabled: {
type: Boolean,
default: true,
},
/**
* @description 粘性布局的距离顶部的距离
*/
offsetTop: {
type: Number,
default: 0,
},
/**
* @description ZIndex
*/
zIndex: {
type: Number,
default: ZIndex.sticky,
},
})
export const stickyEmits = {
/**
* @description 粘性布局状态变化时触发
*/
change: (fixed: boolean) => isBoolean(fixed),
}
export type StickyProps = ExtractPropTypes<typeof stickyProps>
export type StickyEmits = typeof stickyEmits
+34
View File
@@ -0,0 +1,34 @@
<script lang="ts" setup>
import { stickyEmits, stickyProps } from './sticky'
import { useSticky, useStickyCustomStyle } from './composables'
const props = defineProps(stickyProps)
defineEmits(stickyEmits)
const {
componentId,
supportCSSSticky,
stickyStatus,
stickyDistance,
stickyContainerRect,
} = useSticky(props)
const { ns, stickyStyle, stickyContentStyle } = useStickyCustomStyle(
props,
supportCSSSticky,
stickyDistance,
stickyStatus,
stickyContainerRect
)
</script>
<template>
<view :id="componentId" :class="[ns.b()]" :style="stickyStyle">
<view :class="[ns.e('content')]" :style="stickyContentStyle">
<slot />
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/sticky.scss';
</style>
+5
View File
@@ -0,0 +1,5 @@
export type StickyRectInfo = {
width: string | number
height: string | number
left: number
}