高效开发大屏可视化项目第二弹:入场动画和无缝滚动

cfed8e996f84060d90b7d5c23a1f7c38.png

前端Q

我是winty,专注分享前端知识和各类前端资源,乐于分享各种有趣的事,关注我,一起做个有趣的人~

公众号

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

这是一个基于Vue 3、Echarts、高德地图和Pinia开发的大屏可视化项目,提供了如下功能

  • 大屏适配

  • 图表组件(Echarts)封装

  • 高德地图组件封装

  • 拖拽布局

  • 入场动画

  • 无缝滚动

源码地址:https://github.com/vangleer/es-big-screen

前言

上一篇文章分享了大屏适配、图表组件(Echarts)封装、拖拽布局以及地图组件等功能的封装

高效开发大屏可视化项目:公共组件封装指南

今天主要分享大屏入场动画和无缝滚动相关功能的实现

入场动画

  • 最终效果展示

d604c9793235fe2a2d7e1ab81a13f635.jpeg
06.gif

主要使用了CSS动画

Header 动画

src/views/screen/components/Header.vue 一个简单的淡入动画

.es-screen-header {
 animation: fade 3s;
}
@keyframes fade {
 from {
  opacity: 0;
 }
 to {
  opacity: 1;
 }
}

Left 左侧动画

src/views/screen/components/left/index.vue

.es-screen-left-item {
 /* ... */
 &:nth-child(1) {
  height: 550px;
  animation-duration: .8s; // 新增
 }
 &:nth-child(2) {
  animation-duration: 1.5s; // 新增
 }
}
@keyframes slide {
  0% {
    transform: translateX(-100%);
  }
  80% {
    transform: translateX(20px);
  }
  100% {
    transform: translateX(0);
  }
}

左侧定义一个滑入动画,从左侧-100%进入到20px最后回到0原来的位置,这样定义会有一个弹的效果

Right 右侧动画

src/views/screen/components/right/index.vue

.es-screen-right-item {
 /* ... */
 animation-name: slide;
 &:nth-child(1) {
  animation-duration: 0.5s; // 新增
 }
 &:nth-child(2) {
  animation-duration: 1s; // 新增
 }
 &:nth-child(3) {
  animation-duration: 1.5s; // 新增
 }
}

@keyframes slide {
  0% {
    transform: translateX(100%);
  }
  80% {
    transform: translateX(-20px);
  }
  100% {
    transform: translateX(0);
  }
}

与左侧类似,只是方向变了

Center 中间地图入场动画

src/views/screen/components/center/index.vue

.es-center {
 animation: slideAndFade 1.5s; // 新增
}

@keyframes slideAndFade {
  0% {
    transform: translateY(218px);
  opacity: 0;
  }
  100% {
    transform: translateX(0);
  opacity: 1;
  }
}

从下往上移动,移动的同时淡入显示

无缝滚动封装

无缝滚动在大屏可视化项目中非常常见,本小节使用animejs实现了一个支持横纵无缝滚动的自定义钩子

先安装一下 animejs 的依赖

yarn add animejs

直接上代码

import { onMounted, shallowRef, Ref } from 'vue'
import anime from 'animejs/lib/anime.es.js'

export type OptionsType = {
 direction?: 'horizontal' | 'vertical'
 gap?: number
 duration?: number
}

export function useSeamlessScroll(listRef: Ref<HTMLElement | null>, options: OptionsType = {}) {
 const {
  direction = 'horizontal',
  gap = 10,
  duration = 10000
 } = options
 const animation = shallowRef<ReturnType<typeof anime>>(null)

 function init() {
  const isHorizontal = direction === 'horizontal'

  const translateKey = isHorizontal ? 'translateX' : 'translateY'

  const transKey = isHorizontal ? 'x' : 'y'

  // items
  const children = listRef.value?.children || []
  if (!children.length) return

  // 第一个元素
  const firstEl =  children[0] as HTMLElement
  const firstDiff = (isHorizontal ? firstEl.offsetWidth : firstEl.offsetHeight ) + gap

  // 默认将list元素向左或向上移动一个item的距离
  listRef.value!.style.transform = `${translateKey}(-${firstDiff}px)`

  const transList: any = []
  let total = 0 // 总宽/高
  // 设置初始位置
  anime.set(children, {
   [translateKey]: (el: HTMLElement, i) => {

    const distance = (isHorizontal ? el.offsetWidth : el.offsetHeight ) + gap
    total += distance

    const diff = (i - 1) * distance
    transList[i] = { [transKey]: diff }
    return diff
   }
  })
  // 设置list容器的宽或高
  listRef.value!.style[isHorizontal ? 'width' : 'height'] = total + 'px'

  // 添加动画
  animation.value = anime({
   targets: transList,
   duration,
   easing: 'linear',
   direction: isHorizontal ? undefined : 'reverse',
   [transKey]: `+=${total}`,
   loop: true,
   update: () => {
    anime.set(children, {
     [translateKey]: (el, i) => {
      return transList[i][transKey] % total
     }
    })
   }
  })
 }
 // 暂停
 function pause() {
  animation.value!.pause()
 }
 // 停止
 function play() {
  animation.value!.play()
 }

 onMounted(() => {
  init()
 })

 return {
  listRef,
  pause,
  play,
  animation
 }
}

useSeamlessScroll 接受两个参数:listRefoptionslistRef 是一个 Ref 对象,用于引用滚动列表的 DOM 元素。options 是一个配置对象,可以设置滚动的方向、间隙和持续时间。

步骤解析:

  1. 根据传入的滚动方向,确定 translateKeytransKeytranslateKey 表示 CSS 中的移动方向,transKey 表示animejs中的x/y轴方向。

  2. 获取滚动列表的子元素,并计算第一个元素的偏移量。因为默认从从第二个元素开始,这样初始移动是才不会出现空白

  3. 初始化滚动列表的位置,将其向左(横向滚动)或向上(纵向滚动)移动一个元素的距离。

  4. 遍历子元素,计算每个元素的偏移量,并将其设置为初始位置。

  5. 根据滚动方向,设置滚动列表容器的宽度或高度。

  6. 使用 animejs 库实现无缝滚动效果,在动画更新时,根据计算出的偏移量更新子元素的位置

使用 useSeamlessScroll

<template>
 <div class="es-center-bottom">
  <div ref="listRef" class="es-bottom-list">
   <div v-for="item in 10" class="es-bottom-item">
    {{ item }}
   </div>
  </div>
 </div>
</template>

<script setup lang='ts'>
import { ref } from 'vue'
import { useSeamlessScroll } from '@/utils/useSeamlessScroll'

const listRef = ref()
useSeamlessScroll(listRef)
</script>

<style lang='scss' scoped>
.es-center-bottom {
 position: relative;
 width: 100%;
 overflow: hidden;
 height: 150px;
 .es-bottom-item {
  position: absolute;
  top: 0;
  left: 0;
  width: 170px;
  height: 150px;
  display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  background-color: var(--es-block-bg);
  font-size: 22px;
    font-weight: 600;
  .es-item-text {
      margin-top: 16px;
    }
 }
}
</style>

可以看到用了两层包裹列表,最外面一层的作用是防止溢出,第二层才是偏移的列表元素

21de05c1889993f1930d4a44d48bbcfa.jpeg
08.gif

抽离成组件简化使用方式

新建 src/components/SeamlessScroll.vue

<template>
 <div class="es-seamless-scroll">
  <div ref="listRef" class="es-seamless-scroll-list">
   <slot />
  </div>
 </div>
</template>

<script setup lang='ts'>
import { useSeamlessScroll, OptionsType } from '@/utils/useSeamlessScroll'
import { PropType, ref } from 'vue'

const props = defineProps({
 width: {
  type: [String, Number]
 },
 height: {
  type: [String, Number]
 },
 option: {
  type: Object as PropType<OptionsType>,
  default: () => ({})
 }
})
const listRef = ref()
useSeamlessScroll(listRef, props.option)

</script>

<style lang='scss' scoped>
.es-seamless-scroll {
 overflow: hidden;
 width: 100%;
 height: 100%;
}
</style>

使用组件

<template>
 <SeamlessScroll class="es-center-bottom">
  <div v-for="item in 10" class="es-bottom-item">
   {{ item }}
  </div>
 </SeamlessScroll>
</template>

<script setup lang='ts'>
import SeamlessScroll from '@/components/SeamlessScroll.vue'
</script>

<style lang='scss' scoped>
.es-center-bottom {
 position: relative;
 width: 100%;
 overflow: hidden;
 height: 150px;
 .es-bottom-item {
  position: absolute;
  top: 0;
  left: 0;
  width: 170px;
  height: 150px;
  display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  background-color: var(--es-block-bg);
  font-size: 22px;
    font-weight: 600;
  .es-item-text {
      margin-top: 16px;
    }
 }
}
</style>

效果和直接使用钩子一样,不过组件使用起来还是要简单一点

  • 组件默认是横向滚动,如果想纵向滚动传入direction为vertical即可

<template>
 <SeamlessScroll class="es-list" :option="{ direction: 'vertical' }">
  <div v-for="item in 15" class="es-item">{{ '热销商品的占比 ' + item }}</div>
 </SeamlessScroll>
</template>

<script setup lang='ts'>
import SeamlessScroll from '@/components/SeamlessScroll.vue'

</script>

<style lang='scss' scoped>
.es-list {
 position: relative;
 width: 100%;
 height: 100%;
 overflow: hidden;
 .es-item {
  position: absolute;
  height: 20px;
 }
}
</style>
0591fe4aff6fe65f198d5404c86cdbe9.jpeg
09.gif

最后

本文介绍了入场动画和无缝滚动方面的实现,你可以根据示例代码进行实践和定制

如果你对该项目模板感兴趣,可以通过源码地址和在线示例链接来获取更多信息,并进行进一步的学习和应用

关于本文

作者:幽月之格

https://juejin.cn/post/7253115535483207737

3981c3e0ae4e7b21b20da4c6d104fc9e.png

往期推荐

抖音前端团队的设计稿转代码 — Semi D2C 实践方案

b17dcf90d887cb4766b7bdb99410d181.png

前端中 JS 发起的请求可以暂停吗?

3850a7124cc0384471ba54b1fd89b80f.png

spa 如何达到ssr 的秒开技术方案——预渲染

e794e720600b10a654cea01dc4541098.png


最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

6d01d2da53bad97e4987af8beaff66b2.png

前端Q

本公众号主要分享一些技术圈(前端圈为主)相关的技术文章、工具资源、学习资料、招聘信息及其他有趣的东西...

公众号

02d60d7c167615c265a37f1e2d18ce0c.jpeg

54d8aad6b4b3b2d1062f10f4f951c97e.png

点个在看支持我吧

3374bec304273919ed0bc63160fdaa9c.gif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值