|
@@ -1,173 +1,58 @@
|
|
|
<script lang="ts">
|
|
<script lang="ts">
|
|
|
-import { reactive, computed, h, ref, Ref, unref, PropType, watch, CSSProperties } from 'vue';
|
|
|
|
|
-import Taro, { usePageScroll, useReady } from '@tarojs/taro';
|
|
|
|
|
|
|
+import { computed, h, ref, CSSProperties } from 'vue';
|
|
|
import { createComponent } from '@/packages/utils/create';
|
|
import { createComponent } from '@/packages/utils/create';
|
|
|
-import { useTaroRect } from '@/packages/utils/useTaroRect';
|
|
|
|
|
const { componentName, create } = createComponent('sticky');
|
|
const { componentName, create } = createComponent('sticky');
|
|
|
export default create({
|
|
export default create({
|
|
|
props: {
|
|
props: {
|
|
|
- position: {
|
|
|
|
|
- type: String,
|
|
|
|
|
- default: 'top'
|
|
|
|
|
- },
|
|
|
|
|
top: {
|
|
top: {
|
|
|
type: [Number, String],
|
|
type: [Number, String],
|
|
|
default: 0
|
|
default: 0
|
|
|
},
|
|
},
|
|
|
- bottom: {
|
|
|
|
|
- type: [Number, String],
|
|
|
|
|
- default: 0
|
|
|
|
|
- },
|
|
|
|
|
- container: {
|
|
|
|
|
- type: Object as PropType<Element>
|
|
|
|
|
- },
|
|
|
|
|
zIndex: {
|
|
zIndex: {
|
|
|
type: [Number, String],
|
|
type: [Number, String],
|
|
|
default: 2000
|
|
default: 2000
|
|
|
|
|
+ },
|
|
|
|
|
+ parentHeight: {
|
|
|
|
|
+ type: [Number],
|
|
|
|
|
+ default: 667
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
emits: ['change', 'scroll'],
|
|
emits: ['change', 'scroll'],
|
|
|
|
|
|
|
|
setup(props, { emit, slots }) {
|
|
setup(props, { emit, slots }) {
|
|
|
const root = ref<HTMLElement>();
|
|
const root = ref<HTMLElement>();
|
|
|
- const query = Taro.createSelectorQuery();
|
|
|
|
|
- const refRandomId = Math.random().toString(36).slice(-8);
|
|
|
|
|
- const state = reactive({
|
|
|
|
|
- width: 0,
|
|
|
|
|
- height: 0,
|
|
|
|
|
- fixed: false,
|
|
|
|
|
- transform: 0
|
|
|
|
|
- });
|
|
|
|
|
|
|
|
|
|
const rootStyle = computed(() => {
|
|
const rootStyle = computed(() => {
|
|
|
- const { fixed, width, height } = state;
|
|
|
|
|
-
|
|
|
|
|
- if (fixed) {
|
|
|
|
|
- return {
|
|
|
|
|
- width: `${width}px`,
|
|
|
|
|
- height: `${height}px`
|
|
|
|
|
- };
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ height: `${props.parentHeight}px`
|
|
|
|
|
+ };
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const stickyStyle = computed(() => {
|
|
const stickyStyle = computed(() => {
|
|
|
- if (!state.fixed) return;
|
|
|
|
|
-
|
|
|
|
|
const style: CSSProperties = {
|
|
const style: CSSProperties = {
|
|
|
- width: `${state.width}px`,
|
|
|
|
|
- height: `${state.height}px`,
|
|
|
|
|
- [props.position]: `${offset.value}px`,
|
|
|
|
|
|
|
+ top: `${props.top}px`,
|
|
|
zIndex: +props.zIndex
|
|
zIndex: +props.zIndex
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- if (state.transform) style.transform = `translate3d(0, ${state.transform}px, 0)`;
|
|
|
|
|
-
|
|
|
|
|
return style;
|
|
return style;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const offset = computed(() => {
|
|
|
|
|
- return props.position === 'top' ? props.top : props.bottom;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const isHidden = (elementRef: HTMLElement | Ref<HTMLElement | undefined>) => {
|
|
|
|
|
- const el = unref(elementRef);
|
|
|
|
|
- if (!el) return false;
|
|
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
|
|
- query
|
|
|
|
|
- .select(`#${el.id}`)
|
|
|
|
|
- .fields(
|
|
|
|
|
- {
|
|
|
|
|
- computedStyle: ['display', 'position']
|
|
|
|
|
- },
|
|
|
|
|
- (res) => {
|
|
|
|
|
- const hidden = res.display === 'none';
|
|
|
|
|
- const parentHidden = el.offsetParent === null && res.position !== 'fixed';
|
|
|
|
|
- resolve(hidden || parentHidden);
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- .exec();
|
|
|
|
|
- });
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const isExistRoot = async () => {
|
|
|
|
|
- const hidden = await isHidden(root);
|
|
|
|
|
- if (!root.value || hidden) return false;
|
|
|
|
|
- return true;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
const renderFixed = () => {
|
|
const renderFixed = () => {
|
|
|
return h(
|
|
return h(
|
|
|
'view',
|
|
'view',
|
|
|
{
|
|
{
|
|
|
style: stickyStyle.value,
|
|
style: stickyStyle.value,
|
|
|
- class: state.fixed ? `${componentName} nut-sticky--fixed` : componentName
|
|
|
|
|
|
|
+ class: `${componentName} nut-sticky--stickyed`
|
|
|
},
|
|
},
|
|
|
slots.default?.()
|
|
slots.default?.()
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const onScroll = async (scrollTop: number) => {
|
|
|
|
|
- if (!isExistRoot()) return;
|
|
|
|
|
-
|
|
|
|
|
- const { container, position } = props;
|
|
|
|
|
-
|
|
|
|
|
- const rootRect = await useTaroRect(root, Taro);
|
|
|
|
|
-
|
|
|
|
|
- if (rootRect.width || rootRect.height) {
|
|
|
|
|
- state.width = rootRect.width;
|
|
|
|
|
- state.height = rootRect.height;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (position === 'top') {
|
|
|
|
|
- if (container) {
|
|
|
|
|
- const containerRect = await useTaroRect(container, Taro);
|
|
|
|
|
- const diff = containerRect.bottom - +offset.value - state.height;
|
|
|
|
|
- state.fixed = +offset.value > rootRect.top && containerRect.bottom > 0;
|
|
|
|
|
- state.transform = diff < 0 ? diff : 0;
|
|
|
|
|
- } else {
|
|
|
|
|
- state.fixed = offset.value > rootRect.top;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (position === 'bottom') {
|
|
|
|
|
- const clientHeight = Taro.getSystemInfoSync().windowHeight;
|
|
|
|
|
- if (container) {
|
|
|
|
|
- const containerRect = await useTaroRect(container, Taro);
|
|
|
|
|
- const diff = clientHeight - containerRect.top - +offset.value - state.height;
|
|
|
|
|
- state.fixed = clientHeight - +offset.value < rootRect.bottom && clientHeight > containerRect.top;
|
|
|
|
|
- state.transform = diff < 0 ? -diff : 0;
|
|
|
|
|
- } else {
|
|
|
|
|
- state.fixed = clientHeight - +offset.value < rootRect.bottom;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- emit('scroll', {
|
|
|
|
|
- top: scrollTop,
|
|
|
|
|
- fixed: state.fixed
|
|
|
|
|
- });
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- watch(
|
|
|
|
|
- () => state.fixed,
|
|
|
|
|
- (val) => {
|
|
|
|
|
- emit('change', val);
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- usePageScroll((res) => {
|
|
|
|
|
- onScroll(res.scrollTop);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- useReady(() => {
|
|
|
|
|
- Taro.nextTick(() => {
|
|
|
|
|
- onScroll(0);
|
|
|
|
|
- });
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
return () => {
|
|
return () => {
|
|
|
return h(
|
|
return h(
|
|
|
'view',
|
|
'view',
|
|
|
{
|
|
{
|
|
|
style: rootStyle.value,
|
|
style: rootStyle.value,
|
|
|
- id: `root-${refRandomId}`,
|
|
|
|
|
ref: root
|
|
ref: root
|
|
|
},
|
|
},
|
|
|
[renderFixed()]
|
|
[renderFixed()]
|