400 lines
8.5 KiB
Vue
400 lines
8.5 KiB
Vue
<script setup>
|
|
import { computed } from "vue";
|
|
|
|
const props = defineProps({
|
|
variant: {
|
|
type: String,
|
|
default: "home",
|
|
validator: (value) => ["home", "detail"].includes(value),
|
|
},
|
|
});
|
|
|
|
// 固定粒子分布营造随机感,避免每次进入页面时因运行时随机数产生位置跳变。
|
|
const sparkSeeds = [
|
|
[1, "dot", 18, 98, -28, 5, 5, 2.3, 0.4],
|
|
[2, "streak", 23, 90, -38, 4, 18, 2, 1.1, true],
|
|
[3, "dot", 28, 112, -17, 6, 6, 2.8, 1.9, true],
|
|
[4, "dot", 32, 104, -12, 12, 12, 2.6, 0.7, true, true],
|
|
[5, "dot", 36, 88, -24, 4, 4, 1.8, 1.4],
|
|
[6, "streak", 40, 116, -8, 4, 20, 2.4, 2.2, true],
|
|
[7, "dot", 44, 96, -16, 7, 7, 2.1, 0.2],
|
|
[8, "dot", 48, 120, -4, 5, 5, 2.9, 1.3, true],
|
|
[9, "dot", 51, 110, 3, 14, 14, 3, 2.7, true, true],
|
|
[10, "streak", 54, 92, 8, 4, 22, 1.9, 0.8],
|
|
[11, "dot", 58, 105, 14, 6, 6, 2.5, 1.6, true],
|
|
[12, "dot", 62, 86, 21, 4, 4, 2, 0.5],
|
|
[13, "dot", 66, 114, 10, 7, 7, 2.7, 2.1, true],
|
|
[14, "streak", 70, 96, 28, 4, 18, 2.2, 1],
|
|
[15, "dot", 74, 108, 20, 11, 11, 2.8, 2.4, true, true],
|
|
[16, "dot", 80, 90, 35, 5, 5, 2.3, 0.3, true],
|
|
[17, "dot", 21, 118, -8, 4, 4, 2.6, 1.7],
|
|
[18, "streak", 27, 84, -31, 3, 16, 1.8, 0.6, true],
|
|
[19, "dot", 34, 122, -3, 6, 6, 2.9, 2.3, true],
|
|
[20, "dot", 42, 102, 5, 5, 5, 2.2, 0.9],
|
|
[21, "dot", 47, 88, -10, 13, 13, 2.7, 1.5, true, true],
|
|
[22, "streak", 56, 120, 16, 4, 21, 2.5, 2.6, true],
|
|
[23, "dot", 64, 100, 24, 7, 7, 2.4, 0.1],
|
|
[24, "dot", 72, 84, 38, 5, 5, 2, 1.2, true],
|
|
[25, "dot", 82, 116, 12, 4, 4, 2.8, 2],
|
|
[26, "dot", 68, 124, 7, 10, 10, 3.1, 2.9, true, true],
|
|
[27, "streak", 77, 98, 33, 4, 19, 2.1, 0.7, true],
|
|
[28, "dot", 29, 94, -20, 7, 7, 2.5, 1.4],
|
|
[29, "dot", 53, 126, 0, 5, 5, 2.3, 2.2, true],
|
|
[30, "dot", 60, 108, 18, 6, 6, 2.6, 0.4],
|
|
];
|
|
|
|
const deviceSparks = computed(() => {
|
|
const isDetail = props.variant === "detail";
|
|
const sizeScale = isDetail ? 1.25 : 1;
|
|
const bottomOffset = isDetail ? 120 : 0;
|
|
|
|
return sparkSeeds.map(([
|
|
id,
|
|
type,
|
|
left,
|
|
bottom,
|
|
angle,
|
|
width,
|
|
height,
|
|
duration,
|
|
delay,
|
|
long = false,
|
|
large = false,
|
|
]) => ({
|
|
id,
|
|
type,
|
|
long,
|
|
large,
|
|
trackStyle: {
|
|
left: `${left}%`,
|
|
bottom: `${bottom + bottomOffset}rpx`,
|
|
transform: `rotate(${angle}deg)`,
|
|
},
|
|
sparkStyle: {
|
|
width: `${width * sizeScale}rpx`,
|
|
height: `${height * sizeScale}rpx`,
|
|
marginLeft: `${-(width * sizeScale) / 2}rpx`,
|
|
animationDuration: `${duration}s`,
|
|
animationDelay: `-${delay}s`,
|
|
},
|
|
}));
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<view
|
|
class="device-online-effects"
|
|
:class="`device-online-effects--${variant}`"
|
|
>
|
|
<view class="device-online-rays-stage">
|
|
<view class="device-online-rays-orbit">
|
|
<view class="device-online-ray device-online-ray--1" />
|
|
<view class="device-online-ray device-online-ray--2" />
|
|
<view class="device-online-ray device-online-ray--3" />
|
|
<view class="device-online-ray device-online-ray--4" />
|
|
<view class="device-online-ray device-online-ray--5" />
|
|
<view class="device-online-ray device-online-ray--6" />
|
|
<view class="device-online-ray device-online-ray--7" />
|
|
</view>
|
|
</view>
|
|
|
|
<view class="device-online-particles">
|
|
<view
|
|
v-for="spark in deviceSparks"
|
|
:key="spark.id"
|
|
class="device-online-spark-track"
|
|
:style="spark.trackStyle"
|
|
>
|
|
<view
|
|
class="device-online-spark"
|
|
:class="[
|
|
`device-online-spark--${spark.type}`,
|
|
{
|
|
'device-online-spark--long': spark.long,
|
|
'device-online-spark--large': spark.large,
|
|
}
|
|
]"
|
|
:style="spark.sparkStyle"
|
|
/>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.device-online-effects {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
z-index: 3;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* 光束沿底座椭圆轨迹扫动,用位移、明暗和横向缩放表达前后纵深。 */
|
|
.device-online-rays-stage {
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 112rpx;
|
|
width: 100%;
|
|
height: 140rpx;
|
|
overflow: hidden;
|
|
transform-origin: 50% 100%;
|
|
}
|
|
|
|
.device-online-rays-orbit {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
transform-origin: 50% 100%;
|
|
animation: device-rays-rotate 6s ease-in-out infinite;
|
|
}
|
|
|
|
.device-online-ray {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
width: 48rpx;
|
|
height: 132rpx;
|
|
margin-left: -24rpx;
|
|
transform-origin: 50% 100%;
|
|
border-radius: 50% 50% 12% 12%;
|
|
background: linear-gradient(
|
|
180deg,
|
|
rgba(255, 255, 244, 0) 0%,
|
|
rgba(255, 254, 239, 0.1) 36%,
|
|
rgba(255, 248, 215, 0.22) 72%,
|
|
rgba(255, 232, 151, 0.36) 100%
|
|
);
|
|
}
|
|
|
|
.device-online-ray--1 {
|
|
opacity: 0.55;
|
|
transform: rotate(-55deg);
|
|
}
|
|
|
|
.device-online-ray--2 {
|
|
width: 68rpx;
|
|
margin-left: -34rpx;
|
|
opacity: 0.72;
|
|
transform: rotate(-36deg);
|
|
}
|
|
|
|
.device-online-ray--3 {
|
|
width: 42rpx;
|
|
margin-left: -21rpx;
|
|
opacity: 0.6;
|
|
transform: rotate(-18deg);
|
|
}
|
|
|
|
.device-online-ray--4 {
|
|
width: 76rpx;
|
|
margin-left: -38rpx;
|
|
opacity: 0.82;
|
|
}
|
|
|
|
.device-online-ray--5 {
|
|
width: 46rpx;
|
|
margin-left: -23rpx;
|
|
opacity: 0.64;
|
|
transform: rotate(19deg);
|
|
}
|
|
|
|
.device-online-ray--6 {
|
|
width: 64rpx;
|
|
margin-left: -32rpx;
|
|
opacity: 0.7;
|
|
transform: rotate(38deg);
|
|
}
|
|
|
|
.device-online-ray--7 {
|
|
width: 44rpx;
|
|
margin-left: -22rpx;
|
|
opacity: 0.56;
|
|
transform: rotate(56deg);
|
|
}
|
|
|
|
.device-online-particles {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
}
|
|
|
|
.device-online-spark-track {
|
|
position: absolute;
|
|
width: 0;
|
|
height: 0;
|
|
transform-origin: center bottom;
|
|
}
|
|
|
|
.device-online-spark {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
background: #fff6bd;
|
|
box-shadow: 0 0 6rpx rgba(255, 225, 112, 0.65);
|
|
animation: device-spark-rise 2.4s linear infinite;
|
|
}
|
|
|
|
.device-online-spark--dot {
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.device-online-spark--streak {
|
|
border-radius: 50%;
|
|
background: linear-gradient(
|
|
180deg,
|
|
rgba(255, 255, 235, 0) 0%,
|
|
rgba(255, 250, 205, 0.75) 45%,
|
|
#ffe379 100%
|
|
);
|
|
}
|
|
|
|
.device-online-spark--long {
|
|
animation-name: device-spark-rise-long;
|
|
}
|
|
|
|
.device-online-spark--large {
|
|
background: #fffbdc;
|
|
box-shadow:
|
|
0 0 10rpx rgba(255, 243, 174, 0.95),
|
|
0 0 20rpx rgba(255, 207, 74, 0.72);
|
|
}
|
|
|
|
.device-online-effects--detail .device-online-rays-stage {
|
|
bottom: 215rpx;
|
|
height: 190rpx;
|
|
transform: scaleX(1.35);
|
|
}
|
|
|
|
.device-online-effects--detail .device-online-ray {
|
|
height: 180rpx;
|
|
}
|
|
|
|
.device-online-effects--detail .device-online-rays-orbit {
|
|
animation-name: device-rays-rotate-detail;
|
|
}
|
|
|
|
.device-online-effects--detail .device-online-spark {
|
|
animation-name: device-spark-rise-detail;
|
|
}
|
|
|
|
.device-online-effects--detail .device-online-spark--long {
|
|
animation-name: device-spark-rise-detail-long;
|
|
}
|
|
|
|
@keyframes device-spark-rise {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(8rpx) scale(0.45);
|
|
}
|
|
16% {
|
|
opacity: 1;
|
|
}
|
|
72% {
|
|
opacity: 0.62;
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-148rpx) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes device-spark-rise-long {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(8rpx) scale(0.4);
|
|
}
|
|
16% {
|
|
opacity: 1;
|
|
}
|
|
72% {
|
|
opacity: 0.55;
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-176rpx) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes device-spark-rise-detail {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(12rpx) scale(0.45);
|
|
}
|
|
16% {
|
|
opacity: 1;
|
|
}
|
|
72% {
|
|
opacity: 0.62;
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-225rpx) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes device-spark-rise-detail-long {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(12rpx) scale(0.4);
|
|
}
|
|
16% {
|
|
opacity: 1;
|
|
}
|
|
72% {
|
|
opacity: 0.55;
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-270rpx) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes device-rays-rotate {
|
|
0%,
|
|
100% {
|
|
opacity: 0.48;
|
|
transform: translate(-42rpx, 4rpx) skewX(-8deg) scaleX(0.82);
|
|
}
|
|
25% {
|
|
opacity: 0.88;
|
|
transform: translate(0, 0) skewX(0deg) scaleX(1.05);
|
|
}
|
|
50% {
|
|
opacity: 0.48;
|
|
transform: translate(42rpx, 4rpx) skewX(8deg) scaleX(0.82);
|
|
}
|
|
75% {
|
|
opacity: 0.24;
|
|
transform: translate(0, 10rpx) skewX(0deg) scaleX(0.68);
|
|
}
|
|
}
|
|
|
|
@keyframes device-rays-rotate-detail {
|
|
0%,
|
|
100% {
|
|
opacity: 0.48;
|
|
transform: translate(-62rpx, 6rpx) skewX(-8deg) scaleX(0.82);
|
|
}
|
|
25% {
|
|
opacity: 0.88;
|
|
transform: translate(0, 0) skewX(0deg) scaleX(1.05);
|
|
}
|
|
50% {
|
|
opacity: 0.48;
|
|
transform: translate(62rpx, 6rpx) skewX(8deg) scaleX(0.82);
|
|
}
|
|
75% {
|
|
opacity: 0.24;
|
|
transform: translate(0, 14rpx) skewX(0deg) scaleX(0.68);
|
|
}
|
|
}
|
|
</style>
|