| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- <script>
- import { onMount, createEventDispatcher, getContext } from 'svelte';
- import Icon from '../icon/Icon.svelte';
- import zh_CN from '../../lang/zh_CN';
- // 定义事件派发器
- // Define event dispatcher
- const dispatch = createEventDispatcher();
- // 当前语言
- // current language
- const currentLang = getContext('STDF_lang') || zh_CN;
- const commonLang = currentLang.common;
- // 通告内容组成的数组
- // Array of notice content
- export let textList = [];
- //左侧内容
- //Left content
- export let leftIcon = { name: 'ri-volume-down-line', size: 20, top: -1 };
- //右侧内容,'close'/'arrow'/'none'
- //Right content, 'close'/'arrow'/'none'
- export let rightIcon = 'close';
- // 通告字体大小
- // Notice font size
- export let fontSize = 'sm';
- // 通告间距
- // Notice spacing
- export let space = 100;
- // 横向滚动速度,单位是 px/s
- // Horizontal scroll speed, unit is px/s
- export let speed = 30;
- // 是否垂直滚动
- // Whether to scroll vertically
- export let vertical = false;
- // 垂直滚动过渡时间
- // Vertical scroll transition time
- export let duration = 500;
- // 垂直滚动间隔时间
- // Vertical scroll interval time
- export let interval = 4;
- // 注入 CSS 名称
- // Inject CSS name
- export let injClass = '';
- //如果 textList 不是数组给出中英文报错
- if (!Array.isArray(textList)) {
- console.error('[STDF NoticeBar error]textList 必须是数组。(textList must be an array.)');
- }
- //如果 textList 为空数组给出中英文报错
- if (textList.length === 0) {
- console.error('[STDF NoticeBar error]textList 不能为空数组。(textList must not be empty.)');
- }
- // 字体大小样式
- // Font size style
- const fontSizeClass = {
- xs: ' text-xs',
- sm: ' text-sm',
- base: ' text-base',
- lg: ' text-lg',
- };
- // 动画时长样式
- // Animation duration style
- const durationClass = {
- '100': ' duration-100',
- '300': ' duration-300',
- '500': ' duration-500',
- '700': ' duration-700',
- '1000': ' duration-1000',
- };
- let left = speed;
- let boxDom = null;
- let outBoxDom = null;
- let boxWidth = 0;
- let outBoxWidth = 0;
- let outBoxHeight = 0;
- let requestAnimationFrameFlag = null;
- let startTime = 0;
- let fps = 0;
- //垂直滚动时的处理
- //Processing when scrolling vertically
- let times = null;
- let currentIndex = 0;
- let textListVertical = [...textList, textList[0]];
- let isTransition = true;
- const stepFun = time => {
- fps = time - startTime;
- startTime = time;
- left -= (speed * fps) / 1000;
- requestAnimationFrameFlag = requestAnimationFrame(stepFun);
- if (left < -boxWidth / 2) {
- left = 0;
- }
- };
- let newTextList = [...textList, ...textList];
- onMount(() => {
- if (!vertical) {
- boxWidth = boxDom.getBoundingClientRect().width;
- }
- outBoxWidth = outBoxDom.getBoundingClientRect().width;
- outBoxHeight = outBoxDom.getBoundingClientRect().height;
- if (!vertical && boxWidth / 2 - space < outBoxWidth) {
- newTextList = textList;
- left = 0;
- return;
- }
- requestAnimationFrameFlag = requestAnimationFrame(stepFun);
- if (vertical) {
- if (textListVertical.length > 1) {
- times = setInterval(() => {
- currentIndex++;
- if (currentIndex === textListVertical.length - 1) {
- setTimeout(() => {
- currentIndex = 0;
- isTransition = false;
- }, duration);
- } else {
- isTransition = true;
- }
- }, interval * 1000);
- }
- }
- return () => {
- if (vertical) {
- clearInterval(times);
- } else {
- cancelAnimationFrame(requestAnimationFrameFlag);
- }
- };
- });
- let isShow = true;
- let isShowClose = true;
- const clickFun = () => {
- if (rightIcon === 'close') {
- isShow = false;
- setTimeout(() => {
- isShowClose = false;
- }, 200);
- }
- dispatch('clickright');
- };
- </script>
- {#if isShowClose}
- <div
- class={`flex justify-between bg-primary/10 text-primary dark:bg-dark/10 dark:text-dark${
- fontSizeClass[fontSize] || fontSizeClass['sm']
- } p-2${rightIcon === 'none' ? ' pr-2' : ' pr-0'} transition-all duration-300${isShow ? '' : ' scale-0'}${
- injClass === '' ? '' : ` ${injClass}`
- }`}
- >
- <div class={`${leftIcon === 'none' ? '' : 'mr-1'}`}>
- {#if leftIcon === 'slot'}
- <slot>{commonLang.slotEmpty}</slot>
- {:else if leftIcon === 'none'}
- <!--none-->
- {:else}
- <Icon {...leftIcon} />
- {/if}
- </div>
- {#if vertical}
- <div class="grow" bind:this={outBoxDom}>
- <div class="relative overflow-hidden" style={`height:${outBoxHeight}px;`}>
- {#each textListVertical as item, i}
- <div
- class={`absolute truncate${isTransition ? ' transition-all' : ' transition-none'}${
- durationClass[duration] || durationClass['500']
- }`}
- style={`top:${-(currentIndex - i) * outBoxHeight}px;width:${outBoxWidth}px`}
- >
- {item}
- </div>
- {/each}
- </div>
- </div>
- {:else}
- <div class="grow overflow-hidden relative" bind:this={outBoxDom}>
- <div class="whitespace-nowrap absolute" style="left:{left}px" bind:this={boxDom}>
- {#each newTextList as item}
- <div class="inline-block" style="margin-right:{space}px">{item}</div>
- {/each}
- </div>
- </div>
- {/if}
- <!-- svelte-ignore a11y-click-events-have-key-events -->
- <!-- svelte-ignore a11y-no-static-element-interactions -->
- <div class={`${rightIcon === 'none' ? '' : 'pl-2 pr-4'}`} on:click={clickFun}>
- {#if rightIcon === 'close'}
- <Icon name="ri-close-line" size={20} top="-1" />
- {:else if rightIcon === 'arrow'}
- <Icon name="ri-arrow-right-s-line" size="20" top="-1" />
- {/if}
- </div>
- </div>
- {/if}
|