wangrong
2026-08-30 761e5c5fa871959f60e15c971c8d83f2b53d0af5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
import { cloneDeep } from 'lodash-es'
 
interface Props {
  color?: string[]
  curStyle: { width: number; height: number }
  scale: number
  animationRatio?: number // 新增:控制动画元素比例
}
 
const props = withDefaults(defineProps<Props>(), {
  color: () => [],
  curStyle: () => ({
    width: 320,
    height: 240
  }),
  animationRatio: 0.6 // 默认60%的元素有动画
})
 
// 尺寸计算
const width = computed(() => props.curStyle.width)
const height = computed(() => props.curStyle.height)
 
// 样式计算
const border_style = computed(() => ({
  width: `${width.value}px`,
  height: `${height.value}px`,
  transform: `scale(${props.scale})`,
  'transform-origin': '0 0',
  'will-change': 'transform' // 提示浏览器优化
}))
 
// 点阵配置
const pointSideLength = 7
const svgWH = ref([300, 35])
const rowNum = 2
const rowPoints = 25
 
// 颜色配置
const defaultColor = ref(['#7acaec', 'transparent'])
const mergedColor = ref<string[]>([])
 
// 计算属性
const halfPointSideLength = computed(() => pointSideLength / 2)
const svgScale = computed(() => [width.value / svgWH.value[0], height.value / svgWH.value[1]])
 
// 点阵位置
const points = ref<number[][]>([])
 
// 预生成动画配置(优化随机性能)
const animationConfigs = computed(() => {
  return points.value.map((_, i) => ({
    shouldAnimate: Math.random() <= props.animationRatio,
    duration: 1 + Math.random(), // 1-2秒
    delay: Math.random() * 2 // 0-2秒
  }))
})
 
const mergeColor = () => {
  mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
}
 
// 计算点阵位置
const calcPointsPosition = () => {
  const [w, h] = svgWH.value
  const horizontalGap = w / (rowPoints + 1)
  const verticalGap = h / (rowNum + 1)
 
  points.value = Array.from({ length: rowNum }, (_, i) =>
    Array.from({ length: rowPoints }, (_, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)])
  ).flat()
}
 
// 响应式更新
const updateLayout = () => {
  calcPointsPosition()
}
 
// 生命周期和监听
onMounted(() => {
  mergeColor()
  updateLayout()
})
 
watch(() => props.color, mergeColor, { immediate: true })
watch([width, height], updateLayout)
</script>
 
<template>
  <div class="dv-decoration-3" :style="border_style">
    <svg
      :width="`${svgWH[0]}px`"
      :height="`${svgWH[1]}px`"
      :style="`transform:scale(${svgScale[0]},${svgScale[1]});`"
      class="decoration-svg"
    >
      <rect
        v-for="(point, i) in points"
        :key="i"
        :fill="mergedColor[0]"
        :x="point[0] - halfPointSideLength"
        :y="point[1] - halfPointSideLength"
        :width="pointSideLength"
        :height="pointSideLength"
        rx="1"
        opacity="0.8"
      >
        <animate
          v-if="animationConfigs[i].shouldAnimate"
          attributeName="fill"
          :values="mergedColor.join(';')"
          :dur="`${animationConfigs[i].duration}s`"
          :begin="`${animationConfigs[i].delay}s`"
          repeatCount="indefinite"
          calcMode="linear"
        />
      </rect>
    </svg>
  </div>
</template>
 
<style lang="less">
.dv-decoration-3 {
  position: relative;
  width: 100%;
  height: 100%;
  /* 启用硬件加速 */
  transform: translateZ(0);
  backface-visibility: hidden;
  contain: content; /* 限制重绘范围 */
 
  .decoration-svg {
    position: absolute;
    transform-origin: left top;
    /* 优化SVG渲染 */
    shape-rendering: optimizeSpeed;
    pointer-events: none;
 
    rect {
      transition: fill 0.3s ease; /* 颜色变化过渡 */
    }
  }
}
</style>