| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- <template>
- <view
- :class="`wd-floating-panel ${customClass} ${safeAreaInsetBottom ? 'is-safe' : ''}`"
- :style="rootStyle"
- @touchstart.passive="handleTouchStart"
- @touchmove.passive="handleTouchMove"
- @touchend="handleTouchEnd"
- @touchcancel="handleTouchEnd"
- >
- <view :class="`wd-floating-panel__header`">
- <view :class="`wd-floating-panel__header-bar`"></view>
- </view>
- <scroll-view
- :class="`wd-floating-panel__content`"
- data-id="content"
- :show-scrollbar="showScrollbar"
- scroll-y
- @touchmove.stop.prevent="handleTouchMove"
- >
- <slot />
- </scroll-view>
- </view>
- </template>
- <script lang="ts">
- export default {
- name: 'wd-floating-panel',
- options: {
- virtualHost: true,
- addGlobalClass: true,
- styleIsolation: 'shared'
- }
- }
- </script>
- <script lang="ts" setup>
- import { computed, onBeforeMount, ref, watch, type CSSProperties } from 'vue'
- import { floatingPanelProps } from './type'
- import { addUnit, closest, objToStyle } from '../common/util'
- import { useTouch } from '../composables/useTouch'
- const touch = useTouch()
- const props = defineProps(floatingPanelProps)
- const emit = defineEmits(['update:height', 'height-change'])
- const heightValue = ref<number>(props.height)
- const DAMP = 0.2 // 阻尼系数
- let startY: number // 起始位置
- const windowHeight = ref<number>(0)
- const dragging = ref<boolean>(false) // 是否正在拖拽
- const boundary = computed(() => ({
- min: props.anchors[0] ? props.anchors[0] : 100,
- max: props.anchors[props.anchors.length - 1] ? props.anchors[props.anchors.length - 1] : Math.round(windowHeight.value * 0.6)
- }))
- const anchors = computed(() => (props.anchors.length >= 2 ? props.anchors : [boundary.value.min, boundary.value.max]))
- const rootStyle = computed(() => {
- const style: CSSProperties = {
- height: addUnit(boundary.value.max),
- transform: `translateY(calc(100% + ${addUnit(-heightValue.value)}))`,
- transition: !dragging.value ? `transform ${props.duration}ms cubic-bezier(0.18, 0.89, 0.32, 1.28)` : 'none'
- }
- return `${objToStyle(style)}${props.customStyle}`
- })
- const updateHeight = (value: number) => {
- heightValue.value = value
- emit('update:height', value)
- }
- const handleTouchStart = (event: TouchEvent) => {
- touch.touchStart(event)
- dragging.value = true
- startY = -heightValue.value
- }
- const handleTouchMove = (event: TouchEvent) => {
- const target = event.currentTarget as any
- if (target.dataset.id == 'content') {
- if (!props.contentDraggable) return
- }
- touch.touchMove(event)
- const moveY = touch.deltaY.value + startY
- updateHeight(-ease(moveY))
- }
- const handleTouchEnd = () => {
- dragging.value = false
- updateHeight(closest(anchors.value, heightValue.value))
- if (heightValue.value !== -startY) {
- emit('height-change', { height: heightValue.value })
- }
- }
- const ease = (y: number) => {
- const absDistance = Math.abs(y)
- const { min, max } = boundary.value
- if (absDistance > max) {
- return -(max + (absDistance - max) * DAMP)
- }
- if (absDistance < min) {
- return -(min - (min - absDistance) * DAMP)
- }
- return y
- }
- watch(
- () => props.height,
- (value) => {
- heightValue.value = value
- }
- )
- watch(
- boundary,
- () => {
- updateHeight(closest(anchors.value, heightValue.value))
- },
- { immediate: true }
- )
- onBeforeMount(() => {
- const { windowHeight: _windowHeight } = uni.getSystemInfoSync()
- windowHeight.value = _windowHeight
- })
- </script>
- <style lang="scss" scoped>
- @import './index.scss';
- </style>
|