wd-floating-panel.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <template>
  2. <view
  3. :class="`wd-floating-panel ${customClass} ${safeAreaInsetBottom ? 'is-safe' : ''}`"
  4. :style="rootStyle"
  5. @touchstart.passive="handleTouchStart"
  6. @touchmove.passive="handleTouchMove"
  7. @touchend="handleTouchEnd"
  8. @touchcancel="handleTouchEnd"
  9. >
  10. <view :class="`wd-floating-panel__header`">
  11. <view :class="`wd-floating-panel__header-bar`"></view>
  12. </view>
  13. <scroll-view
  14. :class="`wd-floating-panel__content`"
  15. data-id="content"
  16. :show-scrollbar="showScrollbar"
  17. scroll-y
  18. @touchmove.stop.prevent="handleTouchMove"
  19. >
  20. <slot />
  21. </scroll-view>
  22. </view>
  23. </template>
  24. <script lang="ts">
  25. export default {
  26. name: 'wd-floating-panel',
  27. options: {
  28. virtualHost: true,
  29. addGlobalClass: true,
  30. styleIsolation: 'shared'
  31. }
  32. }
  33. </script>
  34. <script lang="ts" setup>
  35. import { computed, onBeforeMount, ref, watch, type CSSProperties } from 'vue'
  36. import { floatingPanelProps } from './type'
  37. import { addUnit, closest, objToStyle } from '../common/util'
  38. import { useTouch } from '../composables/useTouch'
  39. const touch = useTouch()
  40. const props = defineProps(floatingPanelProps)
  41. const emit = defineEmits(['update:height', 'height-change'])
  42. const heightValue = ref<number>(props.height)
  43. const DAMP = 0.2 // 阻尼系数
  44. let startY: number // 起始位置
  45. const windowHeight = ref<number>(0)
  46. const dragging = ref<boolean>(false) // 是否正在拖拽
  47. const boundary = computed(() => ({
  48. min: props.anchors[0] ? props.anchors[0] : 100,
  49. max: props.anchors[props.anchors.length - 1] ? props.anchors[props.anchors.length - 1] : Math.round(windowHeight.value * 0.6)
  50. }))
  51. const anchors = computed(() => (props.anchors.length >= 2 ? props.anchors : [boundary.value.min, boundary.value.max]))
  52. const rootStyle = computed(() => {
  53. const style: CSSProperties = {
  54. height: addUnit(boundary.value.max),
  55. transform: `translateY(calc(100% + ${addUnit(-heightValue.value)}))`,
  56. transition: !dragging.value ? `transform ${props.duration}ms cubic-bezier(0.18, 0.89, 0.32, 1.28)` : 'none'
  57. }
  58. return `${objToStyle(style)}${props.customStyle}`
  59. })
  60. const updateHeight = (value: number) => {
  61. heightValue.value = value
  62. emit('update:height', value)
  63. }
  64. const handleTouchStart = (event: TouchEvent) => {
  65. touch.touchStart(event)
  66. dragging.value = true
  67. startY = -heightValue.value
  68. }
  69. const handleTouchMove = (event: TouchEvent) => {
  70. const target = event.currentTarget as any
  71. if (target.dataset.id == 'content') {
  72. if (!props.contentDraggable) return
  73. }
  74. touch.touchMove(event)
  75. const moveY = touch.deltaY.value + startY
  76. updateHeight(-ease(moveY))
  77. }
  78. const handleTouchEnd = () => {
  79. dragging.value = false
  80. updateHeight(closest(anchors.value, heightValue.value))
  81. if (heightValue.value !== -startY) {
  82. emit('height-change', { height: heightValue.value })
  83. }
  84. }
  85. const ease = (y: number) => {
  86. const absDistance = Math.abs(y)
  87. const { min, max } = boundary.value
  88. if (absDistance > max) {
  89. return -(max + (absDistance - max) * DAMP)
  90. }
  91. if (absDistance < min) {
  92. return -(min - (min - absDistance) * DAMP)
  93. }
  94. return y
  95. }
  96. watch(
  97. () => props.height,
  98. (value) => {
  99. heightValue.value = value
  100. }
  101. )
  102. watch(
  103. boundary,
  104. () => {
  105. updateHeight(closest(anchors.value, heightValue.value))
  106. },
  107. { immediate: true }
  108. )
  109. onBeforeMount(() => {
  110. const { windowHeight: _windowHeight } = uni.getSystemInfoSync()
  111. windowHeight.value = _windowHeight
  112. })
  113. </script>
  114. <style lang="scss" scoped>
  115. @import './index.scss';
  116. </style>