NoticeBar.svelte 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <script>
  2. import { onMount, createEventDispatcher, getContext } from 'svelte';
  3. import Icon from '../icon/Icon.svelte';
  4. import zh_CN from '../../lang/zh_CN';
  5. // 定义事件派发器
  6. // Define event dispatcher
  7. const dispatch = createEventDispatcher();
  8. // 当前语言
  9. // current language
  10. const currentLang = getContext('STDF_lang') || zh_CN;
  11. const commonLang = currentLang.common;
  12. // 通告内容组成的数组
  13. // Array of notice content
  14. export let textList = [];
  15. //左侧内容
  16. //Left content
  17. export let leftIcon = { name: 'ri-volume-down-line', size: 20, top: -1 };
  18. //右侧内容,'close'/'arrow'/'none'
  19. //Right content, 'close'/'arrow'/'none'
  20. export let rightIcon = 'close';
  21. // 通告字体大小
  22. // Notice font size
  23. export let fontSize = 'sm';
  24. // 通告间距
  25. // Notice spacing
  26. export let space = 100;
  27. // 横向滚动速度,单位是 px/s
  28. // Horizontal scroll speed, unit is px/s
  29. export let speed = 30;
  30. // 是否垂直滚动
  31. // Whether to scroll vertically
  32. export let vertical = false;
  33. // 垂直滚动过渡时间
  34. // Vertical scroll transition time
  35. export let duration = 500;
  36. // 垂直滚动间隔时间
  37. // Vertical scroll interval time
  38. export let interval = 4;
  39. // 注入 CSS 名称
  40. // Inject CSS name
  41. export let injClass = '';
  42. //如果 textList 不是数组给出中英文报错
  43. if (!Array.isArray(textList)) {
  44. console.error('[STDF NoticeBar error]textList 必须是数组。(textList must be an array.)');
  45. }
  46. //如果 textList 为空数组给出中英文报错
  47. if (textList.length === 0) {
  48. console.error('[STDF NoticeBar error]textList 不能为空数组。(textList must not be empty.)');
  49. }
  50. // 字体大小样式
  51. // Font size style
  52. const fontSizeClass = {
  53. xs: ' text-xs',
  54. sm: ' text-sm',
  55. base: ' text-base',
  56. lg: ' text-lg',
  57. };
  58. // 动画时长样式
  59. // Animation duration style
  60. const durationClass = {
  61. '100': ' duration-100',
  62. '300': ' duration-300',
  63. '500': ' duration-500',
  64. '700': ' duration-700',
  65. '1000': ' duration-1000',
  66. };
  67. let left = speed;
  68. let boxDom = null;
  69. let outBoxDom = null;
  70. let boxWidth = 0;
  71. let outBoxWidth = 0;
  72. let outBoxHeight = 0;
  73. let requestAnimationFrameFlag = null;
  74. let startTime = 0;
  75. let fps = 0;
  76. //垂直滚动时的处理
  77. //Processing when scrolling vertically
  78. let times = null;
  79. let currentIndex = 0;
  80. let textListVertical = [...textList, textList[0]];
  81. let isTransition = true;
  82. const stepFun = time => {
  83. fps = time - startTime;
  84. startTime = time;
  85. left -= (speed * fps) / 1000;
  86. requestAnimationFrameFlag = requestAnimationFrame(stepFun);
  87. if (left < -boxWidth / 2) {
  88. left = 0;
  89. }
  90. };
  91. let newTextList = [...textList, ...textList];
  92. onMount(() => {
  93. if (!vertical) {
  94. boxWidth = boxDom.getBoundingClientRect().width;
  95. }
  96. outBoxWidth = outBoxDom.getBoundingClientRect().width;
  97. outBoxHeight = outBoxDom.getBoundingClientRect().height;
  98. if (!vertical && boxWidth / 2 - space < outBoxWidth) {
  99. newTextList = textList;
  100. left = 0;
  101. return;
  102. }
  103. requestAnimationFrameFlag = requestAnimationFrame(stepFun);
  104. if (vertical) {
  105. if (textListVertical.length > 1) {
  106. times = setInterval(() => {
  107. currentIndex++;
  108. if (currentIndex === textListVertical.length - 1) {
  109. setTimeout(() => {
  110. currentIndex = 0;
  111. isTransition = false;
  112. }, duration);
  113. } else {
  114. isTransition = true;
  115. }
  116. }, interval * 1000);
  117. }
  118. }
  119. return () => {
  120. if (vertical) {
  121. clearInterval(times);
  122. } else {
  123. cancelAnimationFrame(requestAnimationFrameFlag);
  124. }
  125. };
  126. });
  127. let isShow = true;
  128. let isShowClose = true;
  129. const clickFun = () => {
  130. if (rightIcon === 'close') {
  131. isShow = false;
  132. setTimeout(() => {
  133. isShowClose = false;
  134. }, 200);
  135. }
  136. dispatch('clickright');
  137. };
  138. </script>
  139. {#if isShowClose}
  140. <div
  141. class={`flex justify-between bg-primary/10 text-primary dark:bg-dark/10 dark:text-dark${
  142. fontSizeClass[fontSize] || fontSizeClass['sm']
  143. } p-2${rightIcon === 'none' ? ' pr-2' : ' pr-0'} transition-all duration-300${isShow ? '' : ' scale-0'}${
  144. injClass === '' ? '' : ` ${injClass}`
  145. }`}
  146. >
  147. <div class={`${leftIcon === 'none' ? '' : 'mr-1'}`}>
  148. {#if leftIcon === 'slot'}
  149. <slot>{commonLang.slotEmpty}</slot>
  150. {:else if leftIcon === 'none'}
  151. <!--none-->
  152. {:else}
  153. <Icon {...leftIcon} />
  154. {/if}
  155. </div>
  156. {#if vertical}
  157. <div class="grow" bind:this={outBoxDom}>
  158. <div class="relative overflow-hidden" style={`height:${outBoxHeight}px;`}>
  159. {#each textListVertical as item, i}
  160. <div
  161. class={`absolute truncate${isTransition ? ' transition-all' : ' transition-none'}${
  162. durationClass[duration] || durationClass['500']
  163. }`}
  164. style={`top:${-(currentIndex - i) * outBoxHeight}px;width:${outBoxWidth}px`}
  165. >
  166. {item}
  167. </div>
  168. {/each}
  169. </div>
  170. </div>
  171. {:else}
  172. <div class="grow overflow-hidden relative" bind:this={outBoxDom}>
  173. <div class="whitespace-nowrap absolute" style="left:{left}px" bind:this={boxDom}>
  174. {#each newTextList as item}
  175. <div class="inline-block" style="margin-right:{space}px">{item}</div>
  176. {/each}
  177. </div>
  178. </div>
  179. {/if}
  180. <!-- svelte-ignore a11y-click-events-have-key-events -->
  181. <!-- svelte-ignore a11y-no-static-element-interactions -->
  182. <div class={`${rightIcon === 'none' ? '' : 'pl-2 pr-4'}`} on:click={clickFun}>
  183. {#if rightIcon === 'close'}
  184. <Icon name="ri-close-line" size={20} top="-1" />
  185. {:else if rightIcon === 'arrow'}
  186. <Icon name="ri-arrow-right-s-line" size="20" top="-1" />
  187. {/if}
  188. </div>
  189. </div>
  190. {/if}