ivue-material-plus
Version:
A high quality UI components Library with Vue.js
1 lines • 10.3 kB
Source Map (JSON)
{"version":3,"file":"index2.mjs","sources":["../../../../../src/components/ivue-affix/index.vue"],"sourcesContent":["<template>\r\n <div ref=\"wrapper\">\r\n <!-- slot -->\r\n <div ref=\"content\" :class=\"classes\" :style=\"affixStyle\">\r\n <slot></slot>\r\n </div>\r\n <!-- 占位元素 -->\r\n <div v-show=\"affixStyle\" :style=\"placeholderStyle\"></div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\">\r\nimport {\r\n defineComponent,\r\n onMounted,\r\n ref,\r\n computed,\r\n unref,\r\n onUnmounted,\r\n nextTick,\r\n watch,\r\n} from 'vue';\r\nimport { useEventListener } from '@vueuse/core';\r\nimport throttle from 'lodash.throttle';\r\n\r\nimport { AffixStatus } from './types/affix';\r\nimport {\r\n getTargetRect,\r\n getFixedTop,\r\n getFixedBottom,\r\n getDefaultTarget,\r\n} from './utils';\r\n\r\n// type\r\nimport type {\r\n Props,\r\n AffixState,\r\n AffixStyle,\r\n PlaceholderStyle,\r\n} from './types/affix';\r\n\r\nconst prefixCls = 'ivue-affix';\r\n\r\nexport default defineComponent({\r\n name: prefixCls,\r\n // 声明事件\r\n emits: ['on-change'],\r\n props: {\r\n /**\r\n * 距离窗口顶部达到指定偏移量后触发\r\n *\r\n * @type {Number}\r\n */\r\n offsetTop: {\r\n type: Number,\r\n default: 0,\r\n },\r\n /**\r\n * 距离窗口底部达到指定偏移量后触发\r\n *\r\n * @type {Number}\r\n */\r\n offsetBottom: {\r\n type: Number,\r\n },\r\n /**\r\n * addEventListener 原生的 useCapture 选项\r\n *\r\n * @type {Boolean}\r\n */\r\n useCapture: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n /**\r\n * 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数\r\n *\r\n * @type {HTMLElement}\r\n */\r\n target: {\r\n type: HTMLElement,\r\n },\r\n },\r\n // 组合式 API\r\n setup(props: Props, { emit }) {\r\n // ref = wrapper\r\n const wrapper = ref<HTMLDivElement>();\r\n // ref = content\r\n const content = ref<HTMLDivElement>();\r\n\r\n // 可以执行更新位置\r\n const status = ref<AffixStatus | undefined>(AffixStatus.None);\r\n // 是否固定\r\n const fixed = ref<boolean>(false);\r\n // 初始化固定样式\r\n const affixStyle = ref<AffixStyle | undefined>(undefined);\r\n // 初始化占位样式\r\n const placeholderStyle = ref<PlaceholderStyle | undefined>(undefined);\r\n\r\n // 是否添加class设置 fixed\r\n const classes = computed(() => {\r\n return [\r\n {\r\n [`${prefixCls}`]: !!unref(affixStyle),\r\n },\r\n ];\r\n });\r\n\r\n // 获取滚动顶部固定位置\r\n const getOffsetTop = computed(() => {\r\n const { offsetBottom, offsetTop } = props;\r\n\r\n return offsetBottom === undefined && offsetTop === undefined\r\n ? 0\r\n : offsetTop;\r\n });\r\n\r\n // method\r\n\r\n // 初始化数据\r\n const initData = () => {\r\n // 可以执行更新位置\r\n status.value = AffixStatus.Start;\r\n // 初始化固定样式\r\n affixStyle.value = undefined;\r\n // 初始化占位样式\r\n placeholderStyle.value = undefined;\r\n };\r\n\r\n // 获取目标节点默认 window\r\n const getTarget = () => {\r\n const { target } = props;\r\n\r\n // 有设置自定义 target\r\n if (target !== undefined) {\r\n return target;\r\n }\r\n\r\n // 有外部配置滚动监听容器\r\n return getDefaultTarget();\r\n };\r\n\r\n // 节流更新位置\r\n const lazyUpdatePosition = throttle(() => {\r\n const targetNode = getTarget();\r\n\r\n const _affixStyle = unref(affixStyle);\r\n\r\n // 在测量之前检查位置变化以使 Safari 流畅\r\n if (targetNode && _affixStyle) {\r\n const offsetTop = unref(getOffsetTop);\r\n const offsetBottom = props.offsetBottom;\r\n\r\n // 是否有占位节点\r\n if (targetNode && unref(wrapper)) {\r\n // 获取目标节点的元素大小及其相对于视口的位置的信息\r\n const targetRect = getTargetRect(targetNode);\r\n // 获取占位节点的元素大小及其相对于视口的位置的信息\r\n const placeholderReact = getTargetRect(unref(wrapper));\r\n // 固定的顶部\r\n const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);\r\n // 固定到底部\r\n const fixedBottom = getFixedBottom(\r\n placeholderReact,\r\n targetRect,\r\n offsetBottom\r\n );\r\n\r\n // 有固定到顶部 || 有固定到底部\r\n if (\r\n (fixedTop !== undefined && _affixStyle.top === `${fixedTop}px`) ||\r\n (fixedBottom !== undefined &&\r\n _affixStyle.bottom === `${fixedBottom}px`)\r\n ) {\r\n return;\r\n }\r\n }\r\n }\r\n\r\n // 初始化数据\r\n initData();\r\n\r\n // 计算滚动位置\r\n calculateScrollPosition();\r\n });\r\n\r\n // 计算滚动位置\r\n const calculateScrollPosition = () => {\r\n // 获取目标节点\r\n const targetNode = getTarget();\r\n\r\n // status !== Start || 没有固定节点 || 没有外层 || 没有目标节点\r\n if (\r\n unref(status) !== AffixStatus.Start ||\r\n !unref(content) ||\r\n !unref(wrapper) ||\r\n !targetNode\r\n ) {\r\n return;\r\n }\r\n\r\n // 获取滚动顶部固定位置\r\n const offsetTop = getOffsetTop.value;\r\n // 获取滚动到底部固定位置\r\n const offsetBottom = props.offsetBottom;\r\n\r\n // 没有目标元素\r\n if (!targetNode) {\r\n return;\r\n }\r\n\r\n // 设置为不能执行,等待节流方法执行\r\n const newState: Partial<AffixState> = {\r\n status: AffixStatus.None,\r\n };\r\n\r\n // 获取目标节点的元素大小及其相对于视口的位置的信息\r\n const targetRect = getTargetRect(targetNode);\r\n // 获取占位节点的元素大小及其相对于视口的位置的信息\r\n const wrapperRect = getTargetRect(unref(wrapper));\r\n\r\n // 固定的顶部\r\n const fixedTop = getFixedTop(wrapperRect, targetRect, offsetTop);\r\n // 固定到底部\r\n const fixedBottom = getFixedBottom(wrapperRect, targetRect, offsetBottom);\r\n\r\n // 已经固定了不执行\r\n if (\r\n wrapperRect.top === 0 &&\r\n wrapperRect.left === 0 &&\r\n wrapperRect.width === 0 &&\r\n wrapperRect.height === 0\r\n ) {\r\n return;\r\n }\r\n\r\n // 固定顶部 || 固定底部\r\n if (fixedTop !== undefined || fixedBottom !== undefined) {\r\n // 节点固定\r\n newState.affixStyle = {\r\n width: `${wrapperRect.width}px`,\r\n height: `${wrapperRect.height}px`,\r\n };\r\n\r\n // 设置占位节点尺寸\r\n newState.placeholderStyle = {\r\n width: `${wrapperRect.width}px`,\r\n height: `${wrapperRect.height}px`,\r\n };\r\n\r\n // 有固定到顶部数值\r\n if (fixedTop !== undefined) {\r\n newState.affixStyle.top = `${fixedTop}px`;\r\n }\r\n\r\n // 有固定到底部数组\r\n if (fixedBottom !== undefined) {\r\n newState.affixStyle.bottom = `${fixedBottom}px`;\r\n }\r\n }\r\n\r\n // 有设置元素固定节点\r\n newState.fixed = !!newState.affixStyle;\r\n\r\n // 发送监听事件\r\n if (unref(fixed) !== newState.fixed) {\r\n emit('on-change', newState.fixed);\r\n }\r\n\r\n // 可以执行更新位置\r\n status.value = newState.status;\r\n // 初始化固定样式\r\n affixStyle.value = newState.affixStyle;\r\n // 初始化占位样式\r\n placeholderStyle.value = newState.placeholderStyle;\r\n // 是否固定\r\n fixed.value = newState.fixed;\r\n };\r\n\r\n // watch\r\n\r\n // 监听距离窗口顶部达到指定偏移量后触发\r\n watch(\r\n () => props.offsetTop,\r\n () => {\r\n lazyUpdatePosition();\r\n }\r\n );\r\n\r\n // 监听距离窗口底部达到指定偏移量后触发\r\n watch(\r\n () => props.offsetBottom,\r\n () => {\r\n lazyUpdatePosition();\r\n }\r\n );\r\n\r\n // mounted\r\n onMounted(async () => {\r\n await nextTick();\r\n\r\n // 节流初始化\r\n lazyUpdatePosition();\r\n\r\n // 目标节点\r\n const targetNode = getTarget();\r\n\r\n // 缩放\r\n useEventListener(\r\n targetNode,\r\n 'resize',\r\n lazyUpdatePosition,\r\n props.useCapture\r\n );\r\n\r\n // 滚动\r\n useEventListener(\r\n targetNode,\r\n 'scroll',\r\n lazyUpdatePosition,\r\n props.useCapture\r\n );\r\n });\r\n\r\n // onUnmounted\r\n onUnmounted(() => {\r\n // 节流更新位置\r\n lazyUpdatePosition.cancel();\r\n });\r\n\r\n return {\r\n // ref\r\n wrapper,\r\n content,\r\n\r\n // data\r\n affixStyle,\r\n placeholderStyle,\r\n\r\n // computed\r\n classes,\r\n\r\n // methods\r\n lazyUpdatePosition,\r\n };\r\n },\r\n});\r\n</script>\r\n"],"names":["_openBlock","_createElementBlock","_createCommentVNode","_createElementVNode","_normalizeClass","_normalizeStyle","_renderSlot","_withDirectives"],"mappings":";;;;;SAEI,YAAa,IAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,QAAA,EAAA;SACbA,SAEM,EAAA,EAAAC,kBAAA,CAAA,KAAA,EAAA,UAAA,EAAA;AAAA,IAFYC,mBAAA,QAAA,CAAA;AAAA,IAAAC,mBAAO,KAAE,EAAA;AAAA,MAAU,GAAK,EAAA,SAAA;AAAA,MAAA,KAAA,EAAAC,cAAA,CAAA,IAAA,CAAA,OAAA,CAAA;AAAA,MACxC,KAAA,EAAaC,cAAA,CAAA,IAAA,CAAA,UAAA,CAAA;AAAA,KAAA,EAAA;AAAA,MAEfC,UAAA,CAAa,IAAA,CAAA,MAAA,EAAA,SAAA,CAAA;AAAA,OACb,CAAA,CAAA;AAAA,IAAAJ,mBAA+B,4BAAE,CAAA;AAAA,IAAAK,cAAA,CAAAJ,mBAAA,KAAA,EAAA;AAAA,2BAApB,CAAA,IAAA,CAAU,gBAAA,CAAA;AAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,EAAA;AAAA;;;;;;;;"}