index.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <view :class="classes">
  3. <view
  4. v-show="showNoticebar"
  5. class="nut-noticebar__page"
  6. :class="{
  7. 'nut-noticebar__page--withicon': closeMode,
  8. 'nut-noticebar__page--close': closeMode,
  9. 'nut-noticebar__page--wrapable': wrapable
  10. }"
  11. :style="barStyle"
  12. @click="handleClick"
  13. v-if="direction == 'across'"
  14. >
  15. <view class="nut-noticebar__page-lefticon">
  16. <slot name="left-icon">
  17. <Notice size="16px" v-if="leftIcon"></Notice>
  18. </slot>
  19. </view>
  20. <view ref="wrap" class="nut-noticebar__page-wrap">
  21. <view
  22. ref="content"
  23. :class="wrapContentClass"
  24. :style="contentStyle"
  25. @animationend="onAnimationEnd"
  26. @webkitAnimationEnd="onAnimationEnd"
  27. ><slot>{{ text }}</slot>
  28. </view>
  29. </view>
  30. <view v-if="closeMode || $slots['right-icon']" class="nut-noticebar__page-righticon" @click.stop="onClickIcon">
  31. <slot name="right-icon" v-if="$slots['right-icon']"> </slot>
  32. <CircleClose v-else />
  33. </view>
  34. </view>
  35. <view
  36. class="nut-noticebar__vertical"
  37. v-if="scrollList.length > 0 && direction == 'vertical' && showNoticebar"
  38. :style="barStyle"
  39. >
  40. <template v-if="slots.default">
  41. <view class="nut-noticebar__vertical-list" :style="horseLampStyle">
  42. <ScrollItem
  43. v-for="(item, index) in scrollList"
  44. :key="index"
  45. :style="{ height: height + 'px', 'line-height': height + 'px' }"
  46. :item="item"
  47. ></ScrollItem>
  48. </view>
  49. </template>
  50. <template v-else>
  51. <ul class="nut-noticebar__vertical-list" :style="horseLampStyle">
  52. <li
  53. class="nut-noticebar__vertical-item"
  54. v-for="(item, index) in scrollList"
  55. :key="index"
  56. :style="{ height: pxCheck(height) }"
  57. @click="go(item)"
  58. >
  59. {{ item }}
  60. </li>
  61. </ul>
  62. </template>
  63. <view class="go" @click="!slots.rightIcon && handleClickIcon()">
  64. <slot name="right-icon">
  65. <CircleClose v-if="closeMode" :color="color" size="11px" />
  66. </slot>
  67. </view>
  68. </view>
  69. </view>
  70. </template>
  71. <script lang="ts">
  72. import {
  73. toRefs,
  74. onMounted,
  75. onUnmounted,
  76. reactive,
  77. computed,
  78. onActivated,
  79. onDeactivated,
  80. ref,
  81. watch,
  82. h,
  83. Slots
  84. } from 'vue';
  85. import { Notice, CircleClose } from '@nutui/icons-vue';
  86. import { createComponent } from '@/packages/utils/create';
  87. const { componentName, create } = createComponent('noticebar');
  88. import { pxCheck } from '@/packages/utils/pxCheck';
  89. interface StateProps {
  90. wrapWidth: number;
  91. firstRound: boolean;
  92. duration: number;
  93. offsetWidth: number;
  94. showNoticebar: boolean;
  95. animationClass: string;
  96. animate: boolean;
  97. scrollList: Slots[];
  98. distance: number;
  99. timer: null;
  100. keepAlive: boolean;
  101. isCanScroll: null | boolean;
  102. }
  103. export default create({
  104. props: {
  105. // 滚动方向 across 横向 vertical 纵向
  106. direction: {
  107. type: String,
  108. default: 'across'
  109. },
  110. list: {
  111. type: Array,
  112. default: () => {
  113. return [];
  114. }
  115. },
  116. standTime: {
  117. type: Number,
  118. default: 1000
  119. },
  120. complexAm: {
  121. type: Boolean,
  122. default: false
  123. },
  124. height: {
  125. type: Number,
  126. default: 40
  127. },
  128. text: {
  129. type: String,
  130. default: ''
  131. },
  132. closeMode: {
  133. type: Boolean,
  134. default: false
  135. },
  136. wrapable: {
  137. type: Boolean,
  138. default: false
  139. },
  140. leftIcon: { type: Boolean, default: true },
  141. color: {
  142. type: String,
  143. default: ''
  144. },
  145. background: {
  146. type: String,
  147. default: ''
  148. },
  149. delay: {
  150. type: [String, Number],
  151. default: 1
  152. },
  153. scrollable: {
  154. type: Boolean,
  155. default: null
  156. },
  157. speed: {
  158. type: Number,
  159. default: 50
  160. }
  161. },
  162. components: {
  163. ScrollItem: function (props) {
  164. props.item.props.style = props.style;
  165. props.item.key = props.key;
  166. return h(props.item);
  167. },
  168. Notice,
  169. CircleClose
  170. },
  171. emits: ['click', 'close'],
  172. setup(props, { emit, slots }) {
  173. const wrap = ref<null | HTMLElement>(null);
  174. const content = ref<null | HTMLElement>(null);
  175. const state = reactive<StateProps>({
  176. wrapWidth: 0,
  177. firstRound: true,
  178. duration: 0,
  179. offsetWidth: 0,
  180. showNoticebar: true,
  181. animationClass: '',
  182. animate: false,
  183. scrollList: [],
  184. distance: 0,
  185. timer: null,
  186. keepAlive: false,
  187. isCanScroll: null
  188. });
  189. const classes = computed(() => {
  190. const prefixCls = componentName;
  191. return {
  192. [prefixCls]: true
  193. };
  194. });
  195. const isEllipsis = computed(() => {
  196. if (state.isCanScroll == null) {
  197. return props.wrapable;
  198. } else {
  199. return !state.isCanScroll && !props.wrapable;
  200. }
  201. });
  202. const wrapContentClass = computed(() => {
  203. return {
  204. 'nut-noticebar__page-wrap-content': true,
  205. 'nut-ellipsis': isEllipsis.value,
  206. [state.animationClass]: true
  207. };
  208. });
  209. const barStyle = computed(() => {
  210. let style: {
  211. [props: string]: any;
  212. } = {};
  213. props.color && (style.color = props.color);
  214. props.background && (style.background = props.background);
  215. if (props.direction == 'vertical') {
  216. style.height = `${props.height}px`;
  217. }
  218. return style;
  219. });
  220. const contentStyle = computed(() => {
  221. return {
  222. animationDelay: (state.firstRound ? props.delay : 0) + 's',
  223. animationDuration: state.duration + 's',
  224. transform: `translateX(${state.firstRound ? 0 : state.wrapWidth + 'px'})`
  225. };
  226. });
  227. const horseLampStyle = computed(() => {
  228. let styles = {};
  229. if (props.complexAm) {
  230. styles = {
  231. transform: `translateY(${state.distance}px)`
  232. };
  233. } else {
  234. if (state.animate) {
  235. let a = ~~(props.height / props.speed / 4);
  236. styles = {
  237. transition: `all ${a == 0 ? ~~(props.height / props.speed) : a}s`,
  238. 'margin-top': `-${props.height}px`
  239. };
  240. }
  241. }
  242. return styles;
  243. });
  244. watch(
  245. () => props.text,
  246. (value) => {
  247. initScrollWrap(value);
  248. }
  249. );
  250. watch(
  251. () => props.list,
  252. (value) => {
  253. state.scrollList = [].concat(value as any);
  254. }
  255. );
  256. const initScrollWrap = (value: string) => {
  257. if (state.showNoticebar == false) {
  258. return;
  259. }
  260. setTimeout(() => {
  261. if (!wrap.value || !content.value) {
  262. return;
  263. }
  264. const wrapWidth = wrap.value.getBoundingClientRect().width;
  265. const offsetWidth = content.value.getBoundingClientRect().width;
  266. state.isCanScroll = props.scrollable == null ? offsetWidth > wrapWidth : props.scrollable;
  267. console.log(111, state.isCanScroll);
  268. if (state.isCanScroll) {
  269. state.wrapWidth = wrapWidth;
  270. state.offsetWidth = offsetWidth;
  271. state.duration = offsetWidth / props.speed;
  272. state.animationClass = 'play';
  273. } else {
  274. state.animationClass = '';
  275. }
  276. }, 0);
  277. };
  278. const handleClick = (event: Event) => {
  279. emit('click', event);
  280. };
  281. const onClickIcon = (event: Event) => {
  282. if (props.closeMode) {
  283. state.showNoticebar = !props.closeMode;
  284. }
  285. emit('close', event);
  286. };
  287. const onAnimationEnd = () => {
  288. state.firstRound = false;
  289. setTimeout(() => {
  290. state.duration = (state.offsetWidth + state.wrapWidth) / props.speed;
  291. state.animationClass = 'play-infinite';
  292. }, 0);
  293. };
  294. /**
  295. * 利益点滚动方式一
  296. */
  297. const startRollEasy = () => {
  298. showhorseLamp();
  299. (state.timer as any) = setInterval(showhorseLamp, ~~((props.height / props.speed / 4) * 1000) + props.standTime);
  300. };
  301. const showhorseLamp = () => {
  302. state.animate = true;
  303. setTimeout(() => {
  304. state.scrollList.push(state.scrollList[0]);
  305. state.scrollList.shift();
  306. state.animate = false;
  307. }, ~~((props.height / props.speed / 4) * 1000));
  308. };
  309. const startRoll = () => {
  310. (state.timer as any) = setInterval(() => {
  311. let chunk = 100;
  312. for (let i = 0; i < chunk; i++) {
  313. scroll(i, i < chunk - 1 ? false : true);
  314. }
  315. }, props.standTime + 100 * props.speed);
  316. };
  317. const scroll = (n: number, last: boolean) => {
  318. setTimeout(() => {
  319. state.distance -= props.height / 100;
  320. if (last) {
  321. state.scrollList.push(state.scrollList[0]);
  322. state.scrollList.shift();
  323. state.distance = 0;
  324. }
  325. }, n * props.speed);
  326. };
  327. /**
  328. * 点击滚动单元
  329. */
  330. const go = (item: any) => {
  331. emit('click', item);
  332. };
  333. const handleClickIcon = () => {
  334. if (props.closeMode) {
  335. state.showNoticebar = !props.closeMode;
  336. }
  337. emit('close', state.scrollList[0]);
  338. };
  339. onMounted(() => {
  340. if (props.direction == 'vertical') {
  341. if (slots.default) {
  342. state.scrollList = [].concat(slots.default()[0].children as any);
  343. } else {
  344. state.scrollList = [].concat(props.list as any);
  345. }
  346. setTimeout(() => {
  347. props.complexAm ? startRoll() : startRollEasy();
  348. }, props.standTime);
  349. } else {
  350. initScrollWrap(props.text);
  351. }
  352. });
  353. onActivated(() => {
  354. if (state.keepAlive) {
  355. state.keepAlive = false;
  356. }
  357. });
  358. onDeactivated(() => {
  359. state.keepAlive = true;
  360. clearInterval(state.timer as any);
  361. });
  362. onUnmounted(() => {
  363. clearInterval(state.timer as any);
  364. });
  365. return {
  366. ...toRefs(props),
  367. ...toRefs(state),
  368. isEllipsis,
  369. classes,
  370. barStyle,
  371. contentStyle,
  372. horseLampStyle,
  373. wrap,
  374. content,
  375. handleClick,
  376. onClickIcon,
  377. onAnimationEnd,
  378. go,
  379. handleClickIcon,
  380. slots,
  381. pxCheck,
  382. wrapContentClass
  383. };
  384. }
  385. });
  386. </script>