vue3+大屏数据推送+轨道动画效果
vue前端开发动画轨道+websocket数据,动画渲染效果
·
模拟生产线的运动加工过程,个人记录下
1.把页面封装成单独的组件,可以重复调用
2.通过websocket推送并触发动画效果
3.动画运行完毕自行删除
注意:
1.我调整了动画执行时的速度,速度不是一致的,因为业务需求,也能有对比效果
2.当执行到下面时,中间断掉了,因为业务需求只需要在中间消失,可自行设置位置
demo尺寸是按1920x1080的固定尺寸设置的
【demo截图】
【开发时的截图】

【录了个动画效果】
模拟websocket推送+动画实现
组件完整代码
<style lang="less" scoped>
.steel_layer {
position: absolute;
color: rgba(255, 255, 255, 0.8);
width: 1900px;
height: 600px;
// background: red;
// overflow: hidden;
z-index: 2;
border: 1px red solid;
}
.steel_box {
width: 126px;
height: 33px;
background: url('@/assets/images/nwimg/nw-steel.png');
// background: url('~@/assets/img/nwProductionTrack/nw-steel.png');
background-size: contain;
background-position: center;
position: absolute;
left: 160px;
top: 70px;
display: flex;
align-items: center;
}
.name {
font-size: 16px;
text-align: center;
width: 100%;
position: relative;
top: -2px;
left: -2px;
}
.enterFurnace1 {
top: 90px;
left: 170px;
animation: enterFurnace1 5s linear forwards 1;
}
@keyframes enterFurnace1 {
0% {
background: url('@/assets/images/nwimg/nw-steel.png');
// background: url('~@/assets/img/nwProductionTrack/nw-steel.png');
transform: translateX(0px) scale(0.59);
background-size: 100% 100%;
text-align: center;
}
30% {
width: 200px;
// height: 28px;
// line-height: 24px;
background: url('@/assets/images/nwimg/nw-steel.png');
background-size: contain;
background-position: center;
background-size: 100% 100%;
text-align: center;
// background-repeat: no-repeat;
transform: translate(300px) scale(0.59);
// top: 82x;
}
50% {
width: 660px;
// height: 22px;
// line-height: 24px;
background: url('@/assets/images/nwimg/nw-steel.png');
background-size: contain;
background-position: center;
background-size: 100% 100%;
text-align: center;
// background-repeat: no-repeat;
transform: translate(500px) scale(0.59);
// top: 82x;
}
100% {
width: 800px;
// height: 24px;
// line-height: 24px;
background: url('@/assets/images/nwimg/nw-steel.png');
background-size: contain;
background-position: center;
background-size: 100% 100%;
text-align: center;
// background-repeat: no-repeat;
transform: translate(1600px) scale(0.5);
// top: 82x;
}
}
.block-shade {
position: absolute;
top: 290px;
flex-direction: row;
width: 1200px;
height: 234px;
// border: 1px red solid;
left: -10px;
opacity: 0;
// width: 300px;
// clip-path: circle(50%);
.shade-left {
// display: none;
width: 1200px;
height: 234px;
background: url('@/assets/images/nwimg/nw-bg-track.png');
// background: url('~@/assets/img/nwProductionTrack/nw-bg-track.png');
// background-position: center;
// background-repeat: no-repeat;
// position: absolute;
// left: 1px;
}
}
.enterFurnace2 {
left: -11px;
animation: enterFurnace2 5s linear forwards 1;
display: flex;
opacity: 1;
}
@keyframes enterFurnace2 {
0% {
width: 0px;
display: flex;
opacity: 1;
// opacity: 1;
}
50% {
width: 500px;
overflow: hidden;
opacity: 1;
// transform: translate(500px) scale(1);
}
100% {
width: 1200px;
opacity: 1;
// width: 900px;
// height: 24px;
// line-height: 24px;
// transform: translate(2000px) scale(1);
// top: 82x;
}
}
.location-sty {
position: relative;
right: 710px;
top: 290px;
.container {
// display: none;
position: absolute;
top: 0;
right: 0; /* 固定右侧位置 */
width: 1200px; /* 初始宽度 */
overflow: hidden; /* 裁剪左侧超出的内容 */
transition: width 5s; /* 宽度变化动画 */
height: 234px;
}
/* 内容区域:右对齐,禁止换行 */
.content {
width: 1200px;
height: 234px;
background: url('@/assets/images/nwimg/nw-bg-track.png');
// background-size: contain;
// background-position: center;
// background-size: 100% 100%;
}
/* 缩小容器宽度(左侧被裁剪) */
.container.small {
width: 0;
}
}
.none-sty {
opacity: 0;
}
</style>
<template>
<div class="steel_layer">
<!-- <button onclick="document.querySelector('.container').classList.toggle('small')">
切换宽度(裁剪左侧)
</button> -->
<div class="flex">
<div class="steel_box "
@animationend="animationend(item)"
v-for="item in model.steelList"
:class="item.class"
:key="item.billetNo">
<!-- :class="item.class" -->
<div class="name">
{{ item.billetNo }}
</div>
</div>
</div>
<div class="block-shade "
v-for="item in model.steelList2"
:class="item.class"
:key="item.billetNo"
@animationend="animationend(item)">
<div class="shade-left"></div>
</div>
<div class="location-sty ">
<div v-for="item in model.steelList3"
:key="item.billetNo"
class="container"
:class="item.class"
@transitionend.stop="animationend(item)">
<div class="container">
<div class="content"></div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
// import { Component, Vue, Watch, Prop } from 'vue-property-decorator';
import { reactive, watch } from 'vue';
const props = defineProps({
lineNo: {
type: Array,
default: null
}
});
watch(props.lineNo, () => {
model.steelList = [];
});
const model = reactive({
steelList: [] as any,
state2: null,
steelList2: [] as any,
steelList3: [] as any
});
const animation = (data: any) => {
model.steelList.push({
billetNo: data.billetNo,
class: 'enterFurnace1'
});
console.log(model.steelList);
};
const animationend = (data) => {
if (!data) {
return;
}
// 第一段动画结束
if (data.class == 'enterFurnace1') {
data.class = 'enterFurnace2';
model.steelList.splice(model.steelList.indexOf(data), 1);
model.steelList2.push({ billetNo: data.billetNo, class: 'enterFurnace2' });
} else if (data.class == 'enterFurnace2') {
data.class = 'small';
model.steelList2.splice(model.steelList2.indexOf(data), 1);
model.steelList3.push({ billetNo: data.billetNo, class: '' });
setTimeout(() => {
for (let i = 0; i < model.steelList3.length; i++) {
if (model.steelList3[i].billetNo == data.billetNo) {
model.steelList3[i].class = 'small';
}
}
}, 100);
} else if (data.class == 'small') {
model.steelList3.splice(model.steelList3.indexOf(data), 1);
}
};
defineExpose({
animation
});
</script>
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)