update:优化特效
@@ -0,0 +1,399 @@
|
||||
<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>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import DeviceOnlineEffects from "@/components/DeviceOnlineEffects.vue";
|
||||
import Header from "@/components/Header.vue";
|
||||
import ScreenHint from "@/components/ScreenHint.vue";
|
||||
import ModalDialog from "@/components/ModalDialog.vue";
|
||||
@@ -510,10 +511,17 @@ onShow(async () => {
|
||||
src="../../static/home-device/device-platform.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
<DeviceOnlineEffects
|
||||
v-if="isDeviceOnline"
|
||||
variant="detail"
|
||||
/>
|
||||
<image
|
||||
class="device-bow"
|
||||
src="../../static/home-device/device-bow.png"
|
||||
mode="widthFix"
|
||||
:class="{ 'device-bow--online': isDeviceOnline }"
|
||||
:src="isDeviceOnline
|
||||
? '../../static/home-device/device-bow--online.png'
|
||||
: '../../static/home-device/device-bow.png'"
|
||||
:mode="isDeviceOnline ? 'aspectFit' : 'widthFix'"
|
||||
/>
|
||||
</view>
|
||||
|
||||
@@ -948,7 +956,7 @@ onShow(async () => {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10rpx);
|
||||
transform: translateY(-20rpx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -956,6 +964,7 @@ onShow(async () => {
|
||||
position: absolute;
|
||||
top: 214rpx;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
width: 750rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
@@ -964,10 +973,15 @@ onShow(async () => {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
z-index: 4;
|
||||
width: 562rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.device-bow--online {
|
||||
height: 466rpx;
|
||||
}
|
||||
|
||||
.device-heading {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
@@ -6,6 +6,7 @@ import AppFooter from "@/components/AppFooter.vue";
|
||||
import UserHeader from "@/components/UserHeader.vue";
|
||||
import Signin from "@/components/Signin.vue";
|
||||
import BubbleTip from "@/components/BubbleTip.vue";
|
||||
import DeviceOnlineEffects from "@/components/DeviceOnlineEffects.vue";
|
||||
import ModalDialog from "@/components/ModalDialog.vue";
|
||||
import OtaModal from "@/components/OtaModal.vue";
|
||||
|
||||
@@ -60,6 +61,7 @@ const deviceCardAssets = {
|
||||
},
|
||||
online: {
|
||||
bow: "../static/home-device/device-bow--online.png",
|
||||
platform: "../static/home-device/device-platform-glow--online.png",
|
||||
background: "../static/home-device/device-card-bg--bound.png",
|
||||
actionBackground: "../static/home-device/device-action-bg--device.png",
|
||||
},
|
||||
@@ -483,8 +485,28 @@ onShareTimeline(() => {
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<image
|
||||
v-if="deviceCardState === 'online'"
|
||||
class="device-visual-platform-glow"
|
||||
:src="deviceCardAsset.platform"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<DeviceOnlineEffects
|
||||
v-if="deviceCardState === 'online'"
|
||||
variant="home"
|
||||
/>
|
||||
<view
|
||||
v-if="deviceCardState === 'online'"
|
||||
class="device-visual-float-group"
|
||||
>
|
||||
<image
|
||||
class="device-visual-bow device-visual-bow--online"
|
||||
:src="deviceCardAsset.bow"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
</view>
|
||||
<image
|
||||
v-else
|
||||
class="device-visual-bow"
|
||||
:class="{ 'device-visual-bow--floating': deviceCardState === 'online' }"
|
||||
:src="deviceCardAsset.bow"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
@@ -686,6 +708,8 @@ onShareTimeline(() => {
|
||||
}
|
||||
|
||||
.device-visual-background,
|
||||
.device-visual-platform-glow,
|
||||
.device-visual-float-group,
|
||||
.device-visual-bow {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -697,6 +721,26 @@ onShareTimeline(() => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.device-visual-platform-glow {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-origin: center bottom;
|
||||
animation: device-platform-glow 3.6s ease-in-out -1.1s infinite;
|
||||
}
|
||||
|
||||
.device-visual-float-group {
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
width: 100%;
|
||||
height: 388rpx;
|
||||
animation: device-bow-float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.device-visual-bow {
|
||||
@@ -706,17 +750,27 @@ onShareTimeline(() => {
|
||||
height: 388rpx;
|
||||
}
|
||||
|
||||
.device-visual-bow--floating {
|
||||
animation: device-bow-float 3s ease-in-out infinite;
|
||||
.device-visual-bow--online {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@keyframes device-bow-float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) rotate(-0.5deg);
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10rpx) rotate(0.5deg);
|
||||
transform: translateY(-20rpx);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes device-platform-glow {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.92;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +784,7 @@ onShareTimeline(() => {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 14rpx;
|
||||
z-index: 2;
|
||||
z-index: 6;
|
||||
height: 34rpx;
|
||||
color: #ffffff;
|
||||
font-family: Inter-Regular, sans-serif;
|
||||
@@ -802,7 +856,7 @@ onShareTimeline(() => {
|
||||
position: absolute;
|
||||
bottom: 28rpx;
|
||||
left: 50%;
|
||||
z-index: 2;
|
||||
z-index: 6;
|
||||
width: 218rpx;
|
||||
height: 56rpx;
|
||||
justify-content: center;
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 376 KiB |