valaxy-theme-yun 0.14.17 → 0.14.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/App.vue +1 -0
- package/components/YunFireworks.vue +28 -0
- package/features/fireworks.ts +246 -0
- package/layouts/archives.vue +7 -0
- package/layouts/categories.vue +7 -0
- package/layouts/post.vue +16 -0
- package/layouts/tags.vue +7 -0
- package/node/config.ts +5 -0
- package/package.json +5 -3
- package/types/index.d.ts +14 -0
package/App.vue
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useThemeConfig } from 'valaxy/client'
|
3
|
+
import { onMounted } from 'vue'
|
4
|
+
import { createFireworks } from '../features/fireworks'
|
5
|
+
|
6
|
+
const themeConfig = useThemeConfig()
|
7
|
+
|
8
|
+
onMounted(() => {
|
9
|
+
createFireworks({
|
10
|
+
selector: 'canvas.fireworks',
|
11
|
+
colors: themeConfig.value.fireworks.colors,
|
12
|
+
})
|
13
|
+
})
|
14
|
+
</script>
|
15
|
+
|
16
|
+
<template>
|
17
|
+
<canvas class="fireworks" />
|
18
|
+
</template>
|
19
|
+
|
20
|
+
<style>
|
21
|
+
canvas.fireworks {
|
22
|
+
position: fixed;
|
23
|
+
left: 0;
|
24
|
+
top: 0;
|
25
|
+
z-index: 1;
|
26
|
+
pointer-events: none;
|
27
|
+
}
|
28
|
+
</style>
|
@@ -0,0 +1,246 @@
|
|
1
|
+
/**
|
2
|
+
* @see https://codepen.io/juliangarnier/pen/gmOwJX
|
3
|
+
* inherited from hexo-theme-yun
|
4
|
+
* customized by valaxy-theme-yun
|
5
|
+
* @author YunYouJun
|
6
|
+
*/
|
7
|
+
|
8
|
+
import anime from 'animejs/lib/anime.es.js'
|
9
|
+
import { TinyColor } from '@ctrl/tinycolor'
|
10
|
+
|
11
|
+
interface MinMax {
|
12
|
+
min: number
|
13
|
+
max: number
|
14
|
+
}
|
15
|
+
|
16
|
+
interface FireworksConfig {
|
17
|
+
selector: string
|
18
|
+
colors: string[]
|
19
|
+
numberOfParticles: number
|
20
|
+
orbitRadius: MinMax
|
21
|
+
circleRadius: MinMax
|
22
|
+
diffuseRadius: MinMax
|
23
|
+
animeDuration: MinMax
|
24
|
+
}
|
25
|
+
|
26
|
+
export interface Point {
|
27
|
+
x: number
|
28
|
+
y: number
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface Particle extends Point {
|
32
|
+
color: string
|
33
|
+
radius: number
|
34
|
+
endPos: Point
|
35
|
+
draw(): void
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* 创建烟花
|
40
|
+
* @param config
|
41
|
+
*/
|
42
|
+
export function createFireworks(config: Partial<FireworksConfig>) {
|
43
|
+
const defaultColors = ['#66A7DD', '#3E83E1', '#214EC2']
|
44
|
+
|
45
|
+
const {
|
46
|
+
colors = defaultColors,
|
47
|
+
selector = 'canvas.fireworks',
|
48
|
+
// sky blue
|
49
|
+
numberOfParticles = 20,
|
50
|
+
circleRadius = {
|
51
|
+
min: 10,
|
52
|
+
max: 20,
|
53
|
+
},
|
54
|
+
diffuseRadius = {
|
55
|
+
min: 50,
|
56
|
+
max: 100,
|
57
|
+
},
|
58
|
+
orbitRadius = {
|
59
|
+
min: 50,
|
60
|
+
max: 100,
|
61
|
+
},
|
62
|
+
animeDuration = {
|
63
|
+
min: 900,
|
64
|
+
max: 1500,
|
65
|
+
},
|
66
|
+
} = config
|
67
|
+
|
68
|
+
let pointerX = 0
|
69
|
+
let pointerY = 0
|
70
|
+
|
71
|
+
const canvasEl = document.querySelector(selector) as HTMLCanvasElement
|
72
|
+
const ctx = canvasEl.getContext('2d')
|
73
|
+
|
74
|
+
if (!ctx)
|
75
|
+
return
|
76
|
+
|
77
|
+
/**
|
78
|
+
* 设置画布尺寸
|
79
|
+
*/
|
80
|
+
function setCanvasSize(canvasEl: HTMLCanvasElement) {
|
81
|
+
canvasEl.width = window.innerWidth
|
82
|
+
canvasEl.height = window.innerHeight
|
83
|
+
canvasEl.style.width = `${window.innerWidth}px`
|
84
|
+
canvasEl.style.height = `${window.innerHeight}px`
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* update pointer
|
89
|
+
* @param e
|
90
|
+
*/
|
91
|
+
function updateCoords(e: MouseEvent | TouchEvent) {
|
92
|
+
pointerX
|
93
|
+
= 'clientX' in e
|
94
|
+
? e.clientX
|
95
|
+
: (e.touches[0] ? e.touches[0].clientX : e.changedTouches[0].clientX)
|
96
|
+
pointerY
|
97
|
+
= 'clientY' in e
|
98
|
+
? e.clientY
|
99
|
+
: (e.touches[0] ? e.touches[0].clientY : e.changedTouches[0].clientY)
|
100
|
+
}
|
101
|
+
|
102
|
+
function setParticleDirection(p: Point) {
|
103
|
+
const angle = (anime.random(0, 360) * Math.PI) / 180
|
104
|
+
const value = anime.random(
|
105
|
+
diffuseRadius.min,
|
106
|
+
diffuseRadius.max,
|
107
|
+
)
|
108
|
+
const radius = [-1, 1][anime.random(0, 1)] * value
|
109
|
+
return {
|
110
|
+
x: p.x + radius * Math.cos(angle),
|
111
|
+
y: p.y + radius * Math.sin(angle),
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* 在指定位置创建粒子
|
117
|
+
* @param {number} x
|
118
|
+
* @param {number} y
|
119
|
+
* @returns
|
120
|
+
*/
|
121
|
+
function createParticle(x: number, y: number) {
|
122
|
+
const tinyColor = new TinyColor(colors[anime.random(0, colors.length - 1)])
|
123
|
+
tinyColor.setAlpha(anime.random(0.2, 0.8))
|
124
|
+
|
125
|
+
const p: Particle = {
|
126
|
+
x,
|
127
|
+
y,
|
128
|
+
color: tinyColor.toRgbString(),
|
129
|
+
radius: anime.random(circleRadius.min, circleRadius.max),
|
130
|
+
endPos: setParticleDirection({ x, y }),
|
131
|
+
draw: () => {},
|
132
|
+
}
|
133
|
+
|
134
|
+
p.draw = function () {
|
135
|
+
if (!ctx)
|
136
|
+
return
|
137
|
+
|
138
|
+
ctx.beginPath()
|
139
|
+
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true)
|
140
|
+
ctx.fillStyle = p.color
|
141
|
+
ctx.fill()
|
142
|
+
}
|
143
|
+
return p
|
144
|
+
}
|
145
|
+
|
146
|
+
function createCircle(x: number, y: number) {
|
147
|
+
const p = {
|
148
|
+
x,
|
149
|
+
y,
|
150
|
+
color: '#000',
|
151
|
+
radius: 0.1,
|
152
|
+
alpha: 0.5,
|
153
|
+
lineWidth: 6,
|
154
|
+
draw() {},
|
155
|
+
}
|
156
|
+
|
157
|
+
p.draw = () => {
|
158
|
+
if (!ctx)
|
159
|
+
return
|
160
|
+
|
161
|
+
ctx.globalAlpha = p.alpha
|
162
|
+
ctx.beginPath()
|
163
|
+
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true)
|
164
|
+
ctx.lineWidth = p.lineWidth
|
165
|
+
ctx.strokeStyle = p.color
|
166
|
+
ctx.stroke()
|
167
|
+
ctx.globalAlpha = 1
|
168
|
+
}
|
169
|
+
return p
|
170
|
+
}
|
171
|
+
|
172
|
+
function renderParticle(anim: anime.AnimeInstance) {
|
173
|
+
for (let i = 0; i < anim.animatables.length; i++) {
|
174
|
+
const target = anim.animatables[i].target as any as Particle
|
175
|
+
target.draw()
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
function animateParticles(x: number, y: number) {
|
180
|
+
const circle = createCircle(x, y)
|
181
|
+
const particles = []
|
182
|
+
for (let i = 0; i < numberOfParticles; i++)
|
183
|
+
particles.push(createParticle(x, y))
|
184
|
+
|
185
|
+
anime
|
186
|
+
.timeline()
|
187
|
+
.add({
|
188
|
+
targets: particles,
|
189
|
+
x(p: Particle) {
|
190
|
+
return p.endPos.x
|
191
|
+
},
|
192
|
+
y(p: Particle) {
|
193
|
+
return p.endPos.y
|
194
|
+
},
|
195
|
+
radius: 0.1,
|
196
|
+
duration: anime.random(
|
197
|
+
animeDuration.min,
|
198
|
+
animeDuration.max,
|
199
|
+
),
|
200
|
+
easing: 'easeOutExpo',
|
201
|
+
update: renderParticle,
|
202
|
+
})
|
203
|
+
.add(
|
204
|
+
{
|
205
|
+
targets: circle,
|
206
|
+
radius: anime.random(orbitRadius.min, orbitRadius.max),
|
207
|
+
lineWidth: 0,
|
208
|
+
alpha: {
|
209
|
+
value: 0,
|
210
|
+
easing: 'linear',
|
211
|
+
duration: anime.random(600, 800),
|
212
|
+
},
|
213
|
+
duration: anime.random(1200, 1800),
|
214
|
+
easing: 'easeOutExpo',
|
215
|
+
update: renderParticle,
|
216
|
+
},
|
217
|
+
0,
|
218
|
+
)
|
219
|
+
}
|
220
|
+
|
221
|
+
const render = anime({
|
222
|
+
duration: Infinity,
|
223
|
+
update: () => {
|
224
|
+
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height)
|
225
|
+
},
|
226
|
+
})
|
227
|
+
|
228
|
+
document.addEventListener(
|
229
|
+
'mousedown',
|
230
|
+
(e) => {
|
231
|
+
render.play()
|
232
|
+
updateCoords(e)
|
233
|
+
animateParticles(pointerX, pointerY)
|
234
|
+
},
|
235
|
+
false,
|
236
|
+
)
|
237
|
+
|
238
|
+
setCanvasSize(canvasEl)
|
239
|
+
window.addEventListener(
|
240
|
+
'resize',
|
241
|
+
() => {
|
242
|
+
setCanvasSize(canvasEl)
|
243
|
+
},
|
244
|
+
false,
|
245
|
+
)
|
246
|
+
}
|
package/layouts/archives.vue
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
|
+
import { defineWebPage, useSchemaOrg } from '@vueuse/schema-org'
|
2
3
|
import { useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
|
3
4
|
import { useI18n } from 'vue-i18n'
|
4
5
|
|
@@ -8,6 +9,12 @@ const frontmatter = useFrontmatter()
|
|
8
9
|
|
9
10
|
const title = usePostTitle(frontmatter)
|
10
11
|
const site = useSiteStore()
|
12
|
+
|
13
|
+
useSchemaOrg([
|
14
|
+
defineWebPage({
|
15
|
+
'@type': 'CollectionPage',
|
16
|
+
}),
|
17
|
+
])
|
11
18
|
</script>
|
12
19
|
|
13
20
|
<template>
|
package/layouts/categories.vue
CHANGED
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|
3
3
|
import { useCategory, useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
|
4
4
|
import { useI18n } from 'vue-i18n'
|
5
5
|
import { useRoute } from 'vue-router'
|
6
|
+
import { defineWebPage, useSchemaOrg } from '@vueuse/schema-org'
|
6
7
|
|
7
8
|
const { t } = useI18n()
|
8
9
|
|
@@ -29,6 +30,12 @@ const posts = computed(() => {
|
|
29
30
|
})
|
30
31
|
|
31
32
|
const title = usePostTitle(frontmatter)
|
33
|
+
|
34
|
+
useSchemaOrg([
|
35
|
+
defineWebPage({
|
36
|
+
'@type': 'CollectionPage',
|
37
|
+
}),
|
38
|
+
])
|
32
39
|
</script>
|
33
40
|
|
34
41
|
<template>
|
package/layouts/post.vue
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
import { computed } from 'vue'
|
3
3
|
import { useFrontmatter, useFullUrl, useSiteConfig } from 'valaxy'
|
4
4
|
|
5
|
+
import { defineArticle, useSchemaOrg } from '@vueuse/schema-org'
|
6
|
+
|
5
7
|
const siteConfig = useSiteConfig()
|
6
8
|
const frontmatter = useFrontmatter()
|
7
9
|
const url = useFullUrl()
|
@@ -12,6 +14,20 @@ const showSponsor = computed(() => {
|
|
12
14
|
|
13
15
|
return siteConfig.value.sponsor.enable
|
14
16
|
})
|
17
|
+
|
18
|
+
useSchemaOrg(
|
19
|
+
defineArticle({
|
20
|
+
'@type': 'BlogPosting',
|
21
|
+
'headline': frontmatter.value.title,
|
22
|
+
'description': frontmatter.value.description,
|
23
|
+
'author': [
|
24
|
+
{
|
25
|
+
name: siteConfig.value.author.name,
|
26
|
+
url: siteConfig.value.author.link,
|
27
|
+
},
|
28
|
+
],
|
29
|
+
}),
|
30
|
+
)
|
15
31
|
</script>
|
16
32
|
|
17
33
|
<template>
|
package/layouts/tags.vue
CHANGED
@@ -3,8 +3,15 @@ import { useFrontmatter, useInvisibleElement, usePostTitle, useSiteStore, useTag
|
|
3
3
|
import { useI18n } from 'vue-i18n'
|
4
4
|
import { computed, ref } from 'vue'
|
5
5
|
import { useRoute, useRouter } from 'vue-router'
|
6
|
+
import { defineWebPage, useSchemaOrg } from '@vueuse/schema-org'
|
6
7
|
import { useThemeConfig } from '../composables'
|
7
8
|
|
9
|
+
useSchemaOrg([
|
10
|
+
defineWebPage({
|
11
|
+
'@type': 'CollectionPage',
|
12
|
+
}),
|
13
|
+
])
|
14
|
+
|
8
15
|
const route = useRoute()
|
9
16
|
const router = useRouter()
|
10
17
|
|
package/node/config.ts
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "valaxy-theme-yun",
|
3
|
-
"version": "0.14.
|
3
|
+
"version": "0.14.18",
|
4
4
|
"author": {
|
5
5
|
"email": "me@yunyoujun.cn",
|
6
6
|
"name": "YunYouJun",
|
@@ -18,10 +18,12 @@
|
|
18
18
|
"types": "types/index.d.ts",
|
19
19
|
"dependencies": {
|
20
20
|
"@iconify-json/ant-design": "^1.1.5",
|
21
|
-
"@iconify-json/simple-icons": "^1.1.
|
21
|
+
"@iconify-json/simple-icons": "^1.1.47",
|
22
|
+
"animejs": "^3.2.1",
|
22
23
|
"valaxy-addon-waline": "0.1.0"
|
23
24
|
},
|
24
25
|
"devDependencies": {
|
25
|
-
"
|
26
|
+
"@types/animejs": "^3.1.7",
|
27
|
+
"valaxy": "0.14.18"
|
26
28
|
}
|
27
29
|
}
|
package/types/index.d.ts
CHANGED
@@ -109,6 +109,20 @@ export interface ThemeConfig {
|
|
109
109
|
content: string
|
110
110
|
}
|
111
111
|
|
112
|
+
/**
|
113
|
+
* @en - Fireworks when click
|
114
|
+
* @zh - 点击时的烟花效果
|
115
|
+
*/
|
116
|
+
fireworks: {
|
117
|
+
enable: boolean
|
118
|
+
/**
|
119
|
+
* @en - Fireworks colors
|
120
|
+
* @zh - 烟花颜色
|
121
|
+
* @default ['#66A7DD', '#3E83E1', '#214EC2']
|
122
|
+
*/
|
123
|
+
colors?: string[]
|
124
|
+
}
|
125
|
+
|
112
126
|
/**
|
113
127
|
* @en - Pages
|
114
128
|
* @zh - 页面,显示在社交导航栏下方
|