wd-notice-bar.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <template>
  2. <view v-if="show" :class="`wd-notice-bar ${customClass} ${noticeBarClass}`" :style="rootStyle">
  3. <wd-icon v-if="prefix" custom-class="wd-notice-bar__prefix" :name="prefix"></wd-icon>
  4. <slot v-else name="prefix"></slot>
  5. <view class="wd-notice-bar__wrap">
  6. <view class="wd-notice-bar__content" :style="animation" @transitionend="animationEnd" @click="handleClick">
  7. <template v-if="isVertical">
  8. <view v-for="item in textArray" :key="item">{{ item }}</view>
  9. <view v-if="textArray.length > 1">{{ textArray[0] }}</view>
  10. </template>
  11. <slot v-else>{{ currentText }}</slot>
  12. </view>
  13. </view>
  14. <wd-icon v-if="closable" custom-class="wd-notice-bar__suffix" name="close-bold" @click="handleClose"></wd-icon>
  15. <slot v-else name="suffix"></slot>
  16. </view>
  17. </template>
  18. <script lang="ts">
  19. export default {
  20. name: 'wd-notice-bar',
  21. options: {
  22. virtualHost: true,
  23. addGlobalClass: true,
  24. styleIsolation: 'shared'
  25. }
  26. }
  27. </script>
  28. <script lang="ts" setup>
  29. import wdIcon from '../wd-icon/wd-icon.vue'
  30. import { ref, watch, nextTick, computed, getCurrentInstance, type CSSProperties, onMounted, onActivated, onDeactivated, reactive } from 'vue'
  31. import { getRect, isArray, isDef, objToStyle } from '../common/util'
  32. import { type NoticeBarExpose, noticeBarProps } from './types'
  33. const $wrap = '.wd-notice-bar__wrap'
  34. const $content = '.wd-notice-bar__content'
  35. const props = defineProps(noticeBarProps)
  36. const emit = defineEmits(['close', 'next', 'click'])
  37. const wrapWidth = ref<number>(0)
  38. const show = ref<boolean>(true)
  39. const currentIndex = ref<number>(0)
  40. const textArray = computed(() => (Array.isArray(props.text) ? props.text : [props.text]))
  41. const currentText = computed(() => textArray.value[currentIndex.value])
  42. const verticalIndex = ref<number>(0)
  43. const wrapRect = ref<UniApp.NodeInfo | null>(null) // 外层容器节点信息
  44. const contentRect = ref<UniApp.NodeInfo | null>(null) // 内容节点信息
  45. const isHorizontal = computed(() => props.direction === 'horizontal')
  46. const isVertical = computed(() => props.direction === 'vertical')
  47. const transitionState = reactive<CSSProperties>({
  48. transitionProperty: 'unset',
  49. transitionDelay: 'unset',
  50. transitionDuration: 'unset',
  51. transform: 'none',
  52. transitionTimingFunction: 'linear'
  53. })
  54. const animation = computed(() => {
  55. return objToStyle(transitionState)
  56. })
  57. const rootStyle = computed(() => {
  58. const style: CSSProperties = {}
  59. if (isDef(props.color)) {
  60. style.color = props.color
  61. }
  62. if (isDef(props.backgroundColor)) {
  63. style.background = props.backgroundColor
  64. }
  65. return `${objToStyle(style)}${props.customStyle}`
  66. })
  67. const noticeBarClass = computed(() => {
  68. const { type, wrapable, scrollable } = props
  69. let noticeBarClasses: string[] = []
  70. type && noticeBarClasses.push(`is-${type}`)
  71. if (isHorizontal.value) {
  72. !wrapable && !scrollable && noticeBarClasses.push('wd-notice-bar--ellipse')
  73. } else {
  74. noticeBarClasses.push('wd-notice-bar--ellipse')
  75. }
  76. wrapable && !scrollable && noticeBarClasses.push('wd-notice-bar--wrap')
  77. return noticeBarClasses.join(' ')
  78. })
  79. const { proxy } = getCurrentInstance() as any
  80. watch(
  81. () => props.text,
  82. () => {
  83. reset()
  84. },
  85. { deep: true }
  86. )
  87. onMounted(() => {
  88. startTransition()
  89. // #ifdef APP-PLUS
  90. const pages = getCurrentPages()
  91. const currentPage = pages[pages.length - 1]
  92. const currentWebview = currentPage.$getAppWebview!()
  93. currentWebview.addEventListener('hide', () => {
  94. stopTransition()
  95. })
  96. currentWebview.addEventListener('show', () => {
  97. startTransition()
  98. })
  99. // #endif
  100. })
  101. onActivated(() => {
  102. startTransition()
  103. })
  104. onDeactivated(() => {
  105. stopTransition()
  106. })
  107. function reset() {
  108. stopTransition()
  109. startTransition()
  110. }
  111. function startTransition() {
  112. nextTick(() => scroll())
  113. }
  114. function stopTransition() {
  115. transitionState.transitionProperty = 'unset'
  116. transitionState.transitionDelay = 'unset'
  117. transitionState.transitionDuration = 'unset'
  118. transitionState.transform = 'none'
  119. transitionState.transitionTimingFunction = 'linear'
  120. currentIndex.value = 0
  121. verticalIndex.value = 0
  122. }
  123. function handleClose() {
  124. show.value = false
  125. emit('close')
  126. }
  127. function setTransition({ duration, delay, translate }: { duration: number; delay: number; translate: number }) {
  128. transitionState.transitionProperty = 'all'
  129. transitionState.transitionDelay = `${delay}s`
  130. transitionState.transitionDuration = `${duration}s`
  131. transitionState.transform = `${props.direction === 'vertical' ? 'translateY' : 'translateX'}(${translate}px)`
  132. transitionState.transitionTimingFunction = 'linear'
  133. }
  134. function queryRect() {
  135. return Promise.all([getRect($wrap, false, proxy), getRect($content, false, proxy)])
  136. }
  137. async function verticalAnimate(height: number) {
  138. const translate = -(height / (textArray.value.length + 1)) * (currentIndex.value + 1)
  139. setTransition({
  140. duration: height / (textArray.value.length + 1) / props.speed,
  141. delay: props.delay,
  142. translate
  143. })
  144. }
  145. async function scroll() {
  146. const [wRect, cRect] = await queryRect()
  147. if (!wRect.width || !cRect.width || !cRect.height) return
  148. wrapRect.value = wRect
  149. contentRect.value = cRect
  150. wrapWidth.value = wRect.width
  151. if (isHorizontal.value) {
  152. if (props.scrollable) {
  153. setTransition({
  154. duration: cRect.width / props.speed,
  155. delay: props.delay,
  156. translate: -cRect.width
  157. })
  158. }
  159. } else {
  160. if (textArray.value.length > 1) {
  161. verticalAnimate(cRect.height)
  162. }
  163. }
  164. }
  165. function next() {
  166. if (currentIndex.value >= textArray.value.length - 1) {
  167. currentIndex.value = 0
  168. } else {
  169. currentIndex.value++
  170. }
  171. emit('next', currentIndex.value)
  172. }
  173. function animationEnd() {
  174. if (isHorizontal.value) {
  175. setTransition({
  176. duration: 0,
  177. delay: 0,
  178. translate: wrapWidth.value + 1
  179. })
  180. } else {
  181. if (++verticalIndex.value >= textArray.value.length) {
  182. verticalIndex.value = 0
  183. setTransition({
  184. duration: 0,
  185. delay: 0,
  186. translate: 0
  187. })
  188. }
  189. }
  190. const timer = setTimeout(() => {
  191. next() // 更换下一条文本
  192. nextTick(async () => {
  193. try {
  194. const [wRect, cRect] = await queryRect()
  195. wrapRect.value = wRect
  196. contentRect.value = cRect
  197. wrapWidth.value = wRect.width || 0
  198. } catch (error) {
  199. // console.error(error)
  200. }
  201. if (!contentRect.value || !contentRect.value.width || !contentRect.value.height) return
  202. if (isHorizontal.value) {
  203. setTransition({
  204. duration: (wrapWidth.value + contentRect.value.width) / props.speed,
  205. delay: props.delay,
  206. translate: -contentRect.value.width
  207. })
  208. } else {
  209. verticalAnimate(contentRect.value.height)
  210. }
  211. })
  212. clearTimeout(timer)
  213. }, 20)
  214. }
  215. function handleClick() {
  216. const result = isArray(props.text)
  217. ? {
  218. index: currentIndex.value,
  219. text: props.text[currentIndex.value]
  220. }
  221. : {
  222. index: 0,
  223. text: props.text
  224. }
  225. emit('click', result)
  226. }
  227. defineExpose<NoticeBarExpose>({ reset })
  228. </script>
  229. <style lang="scss" scoped>
  230. @import './index.scss';
  231. </style>