valaxy-theme-hairy 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- package/components/HairyFooter.vue +32 -31
- package/components/HairyFooterFish.vue +17 -0
- package/components/fish.js +315 -0
- package/package.json +1 -1
@@ -2,8 +2,8 @@
|
|
2
2
|
import { capitalize, computed } from 'vue'
|
3
3
|
import { useConfig, useThemeConfig } from 'valaxy'
|
4
4
|
import { useI18n } from 'vue-i18n'
|
5
|
-
|
6
5
|
import pkg from 'valaxy/package.json'
|
6
|
+
import HairyFooterFish from './HairyFooterFish.vue'
|
7
7
|
|
8
8
|
const { t } = useI18n()
|
9
9
|
|
@@ -21,38 +21,39 @@ const footerIcon = computed(() => themeConfig.value.footer.icon)
|
|
21
21
|
</script>
|
22
22
|
|
23
23
|
<template>
|
24
|
-
<footer v-if="themeConfig.footer" class="va-footer
|
25
|
-
<div
|
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
|
-
|
24
|
+
<footer v-if="themeConfig.footer" class="va-footer pt-5" text="center sm" style="color:var(--va-c-text-light); overflow-x: hidden;">
|
25
|
+
<div class="z-5 relative">
|
26
|
+
<div v-if="themeConfig.footer.beian?.enable && themeConfig.footer.beian.icp" class="beian" m="y-2">
|
27
|
+
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">
|
28
|
+
{{ themeConfig.footer.beian.icp }}
|
29
|
+
</a>
|
30
|
+
</div>
|
31
|
+
<div class="copyright flex justify-center items-center" p="1">
|
32
|
+
<span>
|
33
|
+
©
|
34
|
+
<template v-if="!isThisYear">
|
35
|
+
{{ themeConfig.footer.since }} -
|
36
|
+
</template>
|
37
|
+
{{ year }}
|
38
|
+
</span>
|
39
|
+
|
40
|
+
<a v-if="footerIcon" class="inline-flex animate-pulse ml-2" :href="footerIcon.url" target="_blank" :title="footerIcon.title">
|
41
|
+
<div :class="footerIcon.name" />
|
42
|
+
</a>
|
43
|
+
|
44
|
+
<span>{{ config.author.name }}</span>
|
45
|
+
<span class="mx-2">|</span>
|
46
|
+
<span v-if="config.comment.waline" class="flex items-center">
|
47
|
+
<div class="i-ri-eye-fill mr-1" />
|
48
|
+
<span class="waline-pageview-count" data-path="/">1</span>
|
49
|
+
</span>
|
50
|
+
</div>
|
51
|
+
<div v-if="themeConfig.footer.powered" class="powered" m="2">
|
52
|
+
<span v-html="poweredHtml" /> | <span>{{ t('footer.theme') }} - <a :href="themeConfig.pkg.homepage" :title="`valaxy-theme-${config.theme}`" target="_blank">{{ capitalize(config.theme) }}</a> v{{ themeConfig.pkg.version }}</span>
|
53
|
+
</div>
|
54
54
|
</div>
|
55
55
|
|
56
56
|
<slot />
|
57
|
+
<HairyFooterFish />
|
57
58
|
</footer>
|
58
59
|
</template>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { ref } from 'vue'
|
3
|
+
import { useScriptTag } from '@vueuse/core'
|
4
|
+
import { RENDERER } from './fish'
|
5
|
+
|
6
|
+
const fishContainer = ref()
|
7
|
+
|
8
|
+
const tag = useScriptTag('https://cdn.bootcdn.net/ajax/libs/zepto/1.2.0/zepto.min.js')
|
9
|
+
tag.load()
|
10
|
+
.then(() => {
|
11
|
+
RENDERER.init()
|
12
|
+
})
|
13
|
+
</script>
|
14
|
+
|
15
|
+
<template>
|
16
|
+
<div id="jsi-flying-fish-container" ref="fishContainer" style="margin-top: -60px;"></div>
|
17
|
+
</template>
|
@@ -0,0 +1,315 @@
|
|
1
|
+
/* eslint-disable eqeqeq */
|
2
|
+
/* eslint-disable no-var */
|
3
|
+
/* eslint-disable vars-on-top */
|
4
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
5
|
+
/* eslint-disable no-undef */
|
6
|
+
const RENDERER = {
|
7
|
+
POINT_INTERVAL: 5,
|
8
|
+
FISH_COUNT: 3,
|
9
|
+
MAX_INTERVAL_COUNT: 50,
|
10
|
+
INIT_HEIGHT_RATE: 0.5,
|
11
|
+
THRESHOLD: 50,
|
12
|
+
init() {
|
13
|
+
this.setParameters()
|
14
|
+
this.reconstructMethods()
|
15
|
+
this.setup()
|
16
|
+
this.bindEvent()
|
17
|
+
this.render()
|
18
|
+
},
|
19
|
+
setParameters() {
|
20
|
+
this.$window = $(window)
|
21
|
+
this.$container = $('#jsi-flying-fish-container')
|
22
|
+
this.$canvas = $('<canvas />')
|
23
|
+
this.context = this.$canvas.appendTo(this.$container).get(0).getContext('2d')
|
24
|
+
this.points = []
|
25
|
+
this.fishes = []
|
26
|
+
this.watchIds = []
|
27
|
+
},
|
28
|
+
createSurfacePoints() {
|
29
|
+
const count = Math.round(this.width / this.POINT_INTERVAL)
|
30
|
+
this.pointInterval = this.width / (count - 1)
|
31
|
+
this.points.push(new SURFACE_POINT(this, 0))
|
32
|
+
for (let i = 1; i < count; i++) {
|
33
|
+
const point = new SURFACE_POINT(this, i * this.pointInterval)
|
34
|
+
const previous = this.points[i - 1]
|
35
|
+
point.setPreviousPoint(previous)
|
36
|
+
previous.setNextPoint(point)
|
37
|
+
this.points.push(point)
|
38
|
+
}
|
39
|
+
},
|
40
|
+
reconstructMethods() {
|
41
|
+
this.watchWindowSize = this.watchWindowSize.bind(this)
|
42
|
+
this.jdugeToStopResize = this.jdugeToStopResize.bind(this)
|
43
|
+
this.startEpicenter = this.startEpicenter.bind(this)
|
44
|
+
this.moveEpicenter = this.moveEpicenter.bind(this)
|
45
|
+
this.reverseVertical = this.reverseVertical.bind(this)
|
46
|
+
this.render = this.render.bind(this)
|
47
|
+
},
|
48
|
+
setup() {
|
49
|
+
this.points.length = 0
|
50
|
+
this.fishes.length = 0
|
51
|
+
this.watchIds.length = 0
|
52
|
+
this.intervalCount = this.MAX_INTERVAL_COUNT
|
53
|
+
this.width = this.$container.width()
|
54
|
+
this.height = this.$container.height()
|
55
|
+
this.fishCount = (((this.FISH_COUNT * this.width) / 500) * this.height) / 500
|
56
|
+
this.$canvas.attr({ width: this.width, height: this.height })
|
57
|
+
this.reverse = false
|
58
|
+
this.fishes.push(new FISH(this))
|
59
|
+
this.createSurfacePoints()
|
60
|
+
},
|
61
|
+
watchWindowSize() {
|
62
|
+
this.clearTimer()
|
63
|
+
this.tmpWidth = this.$window.width()
|
64
|
+
this.tmpHeight = this.$window.height()
|
65
|
+
this.watchIds.push(setTimeout(this.jdugeToStopResize, this.WATCH_INTERVAL))
|
66
|
+
},
|
67
|
+
clearTimer() {
|
68
|
+
while (this.watchIds.length > 0)
|
69
|
+
clearTimeout(this.watchIds.pop())
|
70
|
+
},
|
71
|
+
jdugeToStopResize() {
|
72
|
+
const width = this.$window.width()
|
73
|
+
const height = this.$window.height()
|
74
|
+
const stopped = width == this.tmpWidth && height == this.tmpHeight
|
75
|
+
this.tmpWidth = width
|
76
|
+
this.tmpHeight = height
|
77
|
+
if (stopped)
|
78
|
+
this.setup()
|
79
|
+
},
|
80
|
+
bindEvent() {
|
81
|
+
this.$window.on('resize', this.watchWindowSize)
|
82
|
+
this.$container.on('mouseenter', this.startEpicenter)
|
83
|
+
this.$container.on('mousemove', this.moveEpicenter)
|
84
|
+
},
|
85
|
+
getAxis(event) {
|
86
|
+
const offset = this.$container.offset()
|
87
|
+
return { x: event.clientX - offset.left + this.$window.scrollLeft(), y: event.clientY - offset.top + this.$window.scrollTop() }
|
88
|
+
},
|
89
|
+
startEpicenter(event) {
|
90
|
+
this.axis = this.getAxis(event)
|
91
|
+
},
|
92
|
+
moveEpicenter(event) {
|
93
|
+
const axis = this.getAxis(event)
|
94
|
+
if (!this.axis)
|
95
|
+
this.axis = axis
|
96
|
+
|
97
|
+
this.generateEpicenter(axis.x, axis.y, axis.y - this.axis.y)
|
98
|
+
this.axis = axis
|
99
|
+
},
|
100
|
+
generateEpicenter(x, y, velocity) {
|
101
|
+
if (y < this.height / 2 - this.THRESHOLD || y > this.height / 2 + this.THRESHOLD)
|
102
|
+
return
|
103
|
+
|
104
|
+
const index = Math.round(x / this.pointInterval)
|
105
|
+
if (index < 0 || index >= this.points.length)
|
106
|
+
return
|
107
|
+
|
108
|
+
this.points[index].interfere(y, velocity)
|
109
|
+
},
|
110
|
+
reverseVertical() {
|
111
|
+
this.reverse = !this.reverse
|
112
|
+
for (let i = 0, count = this.fishes.length; i < count; i++)
|
113
|
+
this.fishes[i].reverseVertical()
|
114
|
+
},
|
115
|
+
controlStatus() {
|
116
|
+
for (let i = 0, count = this.points.length; i < count; i++)
|
117
|
+
this.points[i].updateSelf()
|
118
|
+
|
119
|
+
for (let i = 0, count = this.points.length; i < count; i++)
|
120
|
+
this.points[i].updateNeighbors()
|
121
|
+
|
122
|
+
if (this.fishes.length < this.fishCount && --this.intervalCount == 0) {
|
123
|
+
this.intervalCount = this.MAX_INTERVAL_COUNT
|
124
|
+
this.fishes.push(new FISH(this))
|
125
|
+
}
|
126
|
+
},
|
127
|
+
render() {
|
128
|
+
requestAnimationFrame(this.render)
|
129
|
+
this.controlStatus()
|
130
|
+
this.context.clearRect(0, 0, this.width, this.height)
|
131
|
+
this.context.fillStyle = 'hsl(0, 0%, 95%)'
|
132
|
+
for (let i = 0, count = this.fishes.length; i < count; i++)
|
133
|
+
this.fishes[i].render(this.context)
|
134
|
+
|
135
|
+
this.context.save()
|
136
|
+
this.context.globalCompositeOperation = 'xor'
|
137
|
+
this.context.beginPath()
|
138
|
+
this.context.moveTo(0, this.reverse ? 0 : this.height)
|
139
|
+
for (let i = 0, count = this.points.length; i < count; i++)
|
140
|
+
this.points[i].render(this.context)
|
141
|
+
|
142
|
+
this.context.lineTo(this.width, this.reverse ? 0 : this.height)
|
143
|
+
this.context.closePath()
|
144
|
+
this.context.fill()
|
145
|
+
this.context.restore()
|
146
|
+
},
|
147
|
+
}
|
148
|
+
|
149
|
+
var SURFACE_POINT = function (renderer, x) {
|
150
|
+
this.renderer = renderer
|
151
|
+
this.x = x
|
152
|
+
this.init()
|
153
|
+
}
|
154
|
+
|
155
|
+
SURFACE_POINT.prototype = {
|
156
|
+
SPRING_CONSTANT: 0.03,
|
157
|
+
SPRING_FRICTION: 0.9,
|
158
|
+
WAVE_SPREAD: 0.3,
|
159
|
+
ACCELARATION_RATE: 0.01,
|
160
|
+
init() {
|
161
|
+
this.initHeight = this.renderer.height * this.renderer.INIT_HEIGHT_RATE
|
162
|
+
this.height = this.initHeight
|
163
|
+
this.fy = 0
|
164
|
+
this.force = { previous: 0, next: 0 }
|
165
|
+
},
|
166
|
+
setPreviousPoint(previous) {
|
167
|
+
this.previous = previous
|
168
|
+
},
|
169
|
+
setNextPoint(next) {
|
170
|
+
this.next = next
|
171
|
+
},
|
172
|
+
interfere(y, velocity) {
|
173
|
+
this.fy = this.renderer.height * this.ACCELARATION_RATE * (this.renderer.height - this.height - y >= 0 ? -1 : 1) * Math.abs(velocity)
|
174
|
+
},
|
175
|
+
updateSelf() {
|
176
|
+
this.fy += this.SPRING_CONSTANT * (this.initHeight - this.height)
|
177
|
+
this.fy *= this.SPRING_FRICTION
|
178
|
+
this.height += this.fy
|
179
|
+
},
|
180
|
+
updateNeighbors() {
|
181
|
+
if (this.previous)
|
182
|
+
this.force.previous = this.WAVE_SPREAD * (this.height - this.previous.height)
|
183
|
+
|
184
|
+
if (this.next)
|
185
|
+
this.force.next = this.WAVE_SPREAD * (this.height - this.next.height)
|
186
|
+
},
|
187
|
+
render(context) {
|
188
|
+
if (this.previous) {
|
189
|
+
this.previous.height += this.force.previous
|
190
|
+
this.previous.fy += this.force.previous
|
191
|
+
}
|
192
|
+
if (this.next) {
|
193
|
+
this.next.height += this.force.next
|
194
|
+
this.next.fy += this.force.next
|
195
|
+
}
|
196
|
+
context.lineTo(this.x, this.renderer.height - this.height)
|
197
|
+
},
|
198
|
+
}
|
199
|
+
|
200
|
+
var FISH = function (renderer) {
|
201
|
+
this.renderer = renderer
|
202
|
+
this.init()
|
203
|
+
}
|
204
|
+
|
205
|
+
FISH.prototype = {
|
206
|
+
GRAVITY: 0.4,
|
207
|
+
init() {
|
208
|
+
this.direction = Math.random() < 0.5
|
209
|
+
this.x = this.direction ? this.renderer.width + this.renderer.THRESHOLD : -this.renderer.THRESHOLD
|
210
|
+
this.previousY = this.y
|
211
|
+
this.vx = this.getRandomValue(4, 10) * (this.direction ? -1 : 1)
|
212
|
+
if (this.renderer.reverse) {
|
213
|
+
this.y = this.getRandomValue((this.renderer.height * 1) / 10, (this.renderer.height * 4) / 10)
|
214
|
+
this.vy = this.getRandomValue(2, 5)
|
215
|
+
this.ay = this.getRandomValue(0.05, 0.2)
|
216
|
+
}
|
217
|
+
else {
|
218
|
+
this.y = this.getRandomValue((this.renderer.height * 6) / 10, (this.renderer.height * 9) / 10)
|
219
|
+
this.vy = this.getRandomValue(-5, -2)
|
220
|
+
this.ay = this.getRandomValue(-0.2, -0.05)
|
221
|
+
}
|
222
|
+
this.isOut = false
|
223
|
+
this.theta = 0
|
224
|
+
this.phi = 0
|
225
|
+
},
|
226
|
+
getRandomValue(min, max) {
|
227
|
+
return min + (max - min) * Math.random()
|
228
|
+
},
|
229
|
+
reverseVertical() {
|
230
|
+
this.isOut = !this.isOut
|
231
|
+
this.ay *= -1
|
232
|
+
},
|
233
|
+
controlStatus() {
|
234
|
+
this.previousY = this.y
|
235
|
+
this.x += this.vx
|
236
|
+
this.y += this.vy
|
237
|
+
this.vy += this.ay
|
238
|
+
if (this.renderer.reverse) {
|
239
|
+
if (this.y > this.renderer.height * this.renderer.INIT_HEIGHT_RATE) {
|
240
|
+
this.vy -= this.GRAVITY
|
241
|
+
this.isOut = true
|
242
|
+
}
|
243
|
+
else {
|
244
|
+
if (this.isOut)
|
245
|
+
this.ay = this.getRandomValue(0.05, 0.2)
|
246
|
+
|
247
|
+
this.isOut = false
|
248
|
+
}
|
249
|
+
}
|
250
|
+
else {
|
251
|
+
if (this.y < this.renderer.height * this.renderer.INIT_HEIGHT_RATE) {
|
252
|
+
this.vy += this.GRAVITY
|
253
|
+
this.isOut = true
|
254
|
+
}
|
255
|
+
else {
|
256
|
+
if (this.isOut)
|
257
|
+
this.ay = this.getRandomValue(-0.2, -0.05)
|
258
|
+
|
259
|
+
this.isOut = false
|
260
|
+
}
|
261
|
+
}
|
262
|
+
if (!this.isOut) {
|
263
|
+
this.theta += Math.PI / 20
|
264
|
+
this.theta %= Math.PI * 2
|
265
|
+
this.phi += Math.PI / 30
|
266
|
+
this.phi %= Math.PI * 2
|
267
|
+
}
|
268
|
+
this.renderer.generateEpicenter(this.x + (this.direction ? -1 : 1) * this.renderer.THRESHOLD, this.y, this.y - this.previousY)
|
269
|
+
if ((this.vx > 0 && this.x > this.renderer.width + this.renderer.THRESHOLD) || (this.vx < 0 && this.x < -this.renderer.THRESHOLD))
|
270
|
+
this.init()
|
271
|
+
},
|
272
|
+
render(context) {
|
273
|
+
context.save()
|
274
|
+
context.translate(this.x, this.y)
|
275
|
+
context.rotate(Math.PI + Math.atan2(this.vy, this.vx))
|
276
|
+
context.scale(1, this.direction ? 1 : -1)
|
277
|
+
context.beginPath()
|
278
|
+
context.moveTo(-30, 0)
|
279
|
+
context.bezierCurveTo(-20, 15, 15, 10, 40, 0)
|
280
|
+
context.bezierCurveTo(15, -10, -20, -15, -30, 0)
|
281
|
+
context.fill()
|
282
|
+
context.save()
|
283
|
+
context.translate(40, 0)
|
284
|
+
context.scale(0.9 + 0.2 * Math.sin(this.theta), 1)
|
285
|
+
context.beginPath()
|
286
|
+
context.moveTo(0, 0)
|
287
|
+
context.quadraticCurveTo(5, 10, 20, 8)
|
288
|
+
context.quadraticCurveTo(12, 5, 10, 0)
|
289
|
+
context.quadraticCurveTo(12, -5, 20, -8)
|
290
|
+
context.quadraticCurveTo(5, -10, 0, 0)
|
291
|
+
context.fill()
|
292
|
+
context.restore()
|
293
|
+
context.save()
|
294
|
+
context.translate(-3, 0)
|
295
|
+
context.rotate((Math.PI / 3 + (Math.PI / 10) * Math.sin(this.phi)) * (this.renderer.reverse ? -1 : 1))
|
296
|
+
context.beginPath()
|
297
|
+
if (this.renderer.reverse) {
|
298
|
+
context.moveTo(5, 0)
|
299
|
+
context.bezierCurveTo(10, 10, 10, 30, 0, 40)
|
300
|
+
context.bezierCurveTo(-12, 25, -8, 10, 0, 0)
|
301
|
+
}
|
302
|
+
else {
|
303
|
+
context.moveTo(-5, 0)
|
304
|
+
context.bezierCurveTo(-10, -10, -10, -30, 0, -40)
|
305
|
+
context.bezierCurveTo(12, -25, 8, -10, 0, 0)
|
306
|
+
}
|
307
|
+
context.closePath()
|
308
|
+
context.fill()
|
309
|
+
context.restore()
|
310
|
+
context.restore()
|
311
|
+
this.controlStatus(context)
|
312
|
+
},
|
313
|
+
}
|
314
|
+
|
315
|
+
export { RENDERER }
|