init
This commit is contained in:
8
uni_modules/nutui-uni/components/sticky/index.scss
Normal file
8
uni_modules/nutui-uni/components/sticky/index.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.nut-sticky {
|
||||
/* #ifdef APP-VUE || MP-WEIXIN */
|
||||
|
||||
// 此处默认写sticky属性,是为了给微信和APP通过uni.createSelectorQuery查询是否支持css sticky使用
|
||||
position: sticky;
|
||||
|
||||
/* #endif */
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/sticky/index.ts
Normal file
1
uni_modules/nutui-uni/components/sticky/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './sticky'
|
||||
32
uni_modules/nutui-uni/components/sticky/sticky.ts
Normal file
32
uni_modules/nutui-uni/components/sticky/sticky.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { commonProps, isH5, makeNumericProp, makeStringProp } from '../_utils'
|
||||
|
||||
export const stickyProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 吸顶距离
|
||||
*/
|
||||
offsetTop: makeNumericProp(0),
|
||||
/**
|
||||
* @description 吸附时的层级
|
||||
*/
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 2000,
|
||||
},
|
||||
/**
|
||||
* @description 导航栏高度,自定义导航栏时,需要传入此值
|
||||
* - H5端的导航栏属于“自定义”导航栏的范畴,因为它是非原生的,与普通元素一致
|
||||
*/
|
||||
customNavHeight: makeNumericProp(isH5 ? 44 : 0),
|
||||
/**
|
||||
* @description 是否开启吸顶功能
|
||||
*/
|
||||
disabled: Boolean,
|
||||
/**
|
||||
* @description 吸顶区域的背景颜色
|
||||
*/
|
||||
bgColor: makeStringProp('transparent'),
|
||||
}
|
||||
|
||||
export type StickyProps = ExtractPropTypes<typeof stickyProps>
|
||||
235
uni_modules/nutui-uni/components/sticky/sticky.vue
Normal file
235
uni_modules/nutui-uni/components/sticky/sticky.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ComponentInternalInstance, CSSProperties } from 'vue'
|
||||
import { computed, defineComponent, getCurrentInstance, nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { PREFIX } from '../_constants'
|
||||
import { useRect } from '../_hooks'
|
||||
import { getMainClass, getMainStyle, getPx, getRandomId, pxCheck } from '../_utils'
|
||||
import { stickyProps } from './sticky'
|
||||
|
||||
const props = defineProps(stickyProps)
|
||||
|
||||
const instance = getCurrentInstance() as ComponentInternalInstance
|
||||
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
const elementId = `${componentName}-${getRandomId()}`
|
||||
|
||||
const cssSticky = ref(false)
|
||||
|
||||
const left = ref(0)
|
||||
const width = ref('auto')
|
||||
const height = ref('auto')
|
||||
const fixed = ref(false)
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName)
|
||||
})
|
||||
|
||||
const stickyTop = computed(() => {
|
||||
return Number(getPx(props.offsetTop)) + Number(getPx(props.customNavHeight))
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
const value: CSSProperties = {}
|
||||
|
||||
if (!props.disabled) {
|
||||
if (cssSticky.value) {
|
||||
value.position = 'sticky'
|
||||
value.top = pxCheck(stickyTop.value)
|
||||
value.zIndex = props.zIndex
|
||||
}
|
||||
else {
|
||||
if (!fixed.value || height.value === 'auto') {
|
||||
value.height = 'auto'
|
||||
}
|
||||
else {
|
||||
value.height = `${height.value}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
value.position = 'static'
|
||||
}
|
||||
|
||||
value.backgroundColor = props.bgColor
|
||||
|
||||
return getMainStyle(props, value)
|
||||
})
|
||||
|
||||
const contentStyles = computed(() => {
|
||||
const value: CSSProperties = {}
|
||||
|
||||
if (!cssSticky.value) {
|
||||
value.position = fixed.value ? 'fixed' : 'static'
|
||||
value.top = `${stickyTop.value}px`
|
||||
value.left = `${left.value}px`
|
||||
value.width = width.value === 'auto' ? 'auto' : `${width.value}px`
|
||||
value.zIndex = props.zIndex
|
||||
}
|
||||
|
||||
return value
|
||||
})
|
||||
|
||||
function setFixed(top: number) {
|
||||
// 判断是否出于吸顶条件范围
|
||||
fixed.value = top <= stickyTop.value
|
||||
}
|
||||
|
||||
const observer = shallowRef<UniApp.IntersectionObserver | null>(null)
|
||||
|
||||
function disconnectObserver() {
|
||||
// 断掉观察,释放资源
|
||||
if (observer.value == null)
|
||||
return
|
||||
|
||||
observer.value.disconnect()
|
||||
observer.value = null
|
||||
}
|
||||
|
||||
function observeContent() {
|
||||
// 先断掉之前的观察
|
||||
disconnectObserver()
|
||||
|
||||
const contentObserver = uni.createIntersectionObserver({
|
||||
// 检测的区间范围
|
||||
thresholds: [0.95, 0.98, 1],
|
||||
})
|
||||
|
||||
// 到屏幕顶部的高度时触发
|
||||
contentObserver.relativeToViewport({
|
||||
top: -stickyTop.value,
|
||||
})
|
||||
|
||||
// 绑定观察的元素
|
||||
contentObserver.observe(`#${elementId}`, (res) => {
|
||||
setFixed(res.boundingClientRect.top)
|
||||
})
|
||||
|
||||
observer.value = contentObserver
|
||||
}
|
||||
|
||||
function initObserveContent() {
|
||||
// 获取吸顶内容的高度,用于在js吸顶模式时,给父元素一个填充高度,防止"塌陷"
|
||||
useRect(elementId, instance).then((res) => {
|
||||
left.value = res.left!
|
||||
width.value = String(res.width)
|
||||
height.value = String(res.height)
|
||||
|
||||
nextTick(() => {
|
||||
observeContent()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// H5通过创建元素的形式嗅探是否支持css sticky
|
||||
// 判断浏览器是否支持sticky属性
|
||||
function checkCssStickyForH5() {
|
||||
// 方法内进行判断,避免在其他平台生成无用代码
|
||||
// #ifdef H5
|
||||
const vendorList = ['', '-webkit-', '-ms-', '-moz-', '-o-']
|
||||
const stickyElement = document.createElement('div')
|
||||
for (let i = 0; i < vendorList.length; i++) {
|
||||
stickyElement.style.position = `${vendorList[i]}sticky`
|
||||
if (stickyElement.style.position !== '')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 在APP和微信小程序上,通过uni.createSelectorQuery可以判断是否支持css sticky
|
||||
function checkComputedStyle(): Promise<boolean> {
|
||||
// 方法内进行判断,避免在其他平台生成无用代码
|
||||
// #ifdef APP-VUE || MP-WEIXIN
|
||||
return new Promise((resolve) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(instance)
|
||||
.select(`.${componentName}`)
|
||||
.fields({
|
||||
computedStyle: ['position'],
|
||||
}, () => {})
|
||||
.exec((res) => {
|
||||
resolve(res[0].position === 'sticky')
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
async function checkSupportCssSticky() {
|
||||
// #ifdef H5
|
||||
// H5,一般都是现代浏览器,是支持css sticky的,这里使用创建元素嗅探的形式判断
|
||||
if (checkCssStickyForH5())
|
||||
cssSticky.value = true
|
||||
// #endif
|
||||
|
||||
// 如果安卓版本高于8.0,依然认为是支持css sticky的(因为安卓7在某些机型,可能不支持sticky)
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
if (systemInfo.osName === 'android' && Number.parseInt(systemInfo.osVersion) > 8)
|
||||
cssSticky.value = true
|
||||
|
||||
// APP-Vue和微信平台,通过computedStyle判断是否支持css sticky
|
||||
// #ifdef APP-VUE || MP-WEIXIN
|
||||
cssSticky.value = await checkComputedStyle()
|
||||
// #endif
|
||||
|
||||
// ios上,从ios6开始,都是支持css sticky的
|
||||
if (systemInfo.osName === 'ios')
|
||||
cssSticky.value = true
|
||||
|
||||
// nvue,是支持css sticky的
|
||||
// #ifdef APP-NVUE
|
||||
cssSticky.value = true
|
||||
// #endif
|
||||
}
|
||||
|
||||
function init() {
|
||||
// 判断使用的模式
|
||||
checkSupportCssSticky()
|
||||
|
||||
// 如果不支持css sticky,则使用js方案,此方案性能比不上css方案
|
||||
if (!cssSticky.value) {
|
||||
if (!props.disabled) {
|
||||
initObserveContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
disconnectObserver()
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-sticky`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
:id="elementId"
|
||||
:class="classes"
|
||||
:style="[styles]"
|
||||
>
|
||||
<view
|
||||
class="nut-sticky__content"
|
||||
:style="[contentStyles]"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
</style>
|
||||
Reference in New Issue
Block a user