diff --git a/blog.config.js b/blog.config.js
index f19261e9..c0fcca5e 100644
--- a/blog.config.js
+++ b/blog.config.js
@@ -70,6 +70,14 @@ const BLOG = {
// 樱花飘落特效
SAKURA: process.env.NEXT_PUBLIC_SAKURA || false, // 开关
+ // 漂浮线段特效
+ NEST: process.env.NEXT_PUBLIC_NEST || false, // 开关
+
+ // 动态彩带特效
+ FLUTTERINGRIBBON: process.env.NEXT_PUBLIC_FLUTTERINGRIBBON || false, // 开关
+ // 静态彩带特效
+ RIBBON: process.env.NEXT_PUBLIC_RIBBON || false, // 开关
+
// 星空雨特效 黑夜模式才会生效
STARRY_SKY: process.env.NEXT_PUBLIC_STARRY_SKY || false, // 开关
diff --git a/components/FlutteringRibbon.js b/components/FlutteringRibbon.js
new file mode 100644
index 00000000..2eb817e4
--- /dev/null
+++ b/components/FlutteringRibbon.js
@@ -0,0 +1,307 @@
+/* eslint-disable */
+import React from 'react'
+
+export const FlutteringRibbon = () => {
+ React.useEffect(() => {
+ createFlutteringRibbon()
+ }, [])
+}
+
+/**
+ * 创建连接点
+ * @param config
+ */
+function createFlutteringRibbon() {
+ 'object' == typeof window &&
+ (window.Ribbons = (function () {
+ const t = window,
+ i = document.body,
+ n = document.documentElement
+ var o = function () {
+ if (1 === arguments.length) {
+ if (Array.isArray(arguments[0])) {
+ const t = Math.round(o(0, arguments[0].length - 1))
+ return arguments[0][t]
+ }
+ return o(0, arguments[0])
+ }
+ return 2 === arguments.length
+ ? Math.random() * (arguments[1] - arguments[0]) + arguments[0]
+ : 0
+ }
+ const s = function (o) {
+ const s = Math.max(
+ 0,
+ t.innerWidth || n.clientWidth || i.clientWidth || 0
+ ),
+ e = Math.max(
+ 0,
+ t.innerHeight || n.clientHeight || i.clientHeight || 0
+ )
+ return {
+ width: s,
+ height: e,
+ ratio: s / e,
+ centerx: s / 2,
+ centery: e / 2,
+ scrollx:
+ Math.max(0, t.pageXOffset || n.scrollLeft || i.scrollLeft || 0) -
+ (n.clientLeft || 0),
+ scrolly:
+ Math.max(0, t.pageYOffset || n.scrollTop || i.scrollTop || 0) -
+ (n.clientTop || 0)
+ }
+ },
+ e = function (t, i) {
+ ;(this.x = 0), (this.y = 0), this.set(t, i)
+ }
+ e.prototype = {
+ constructor: e,
+ set: function (t, i) {
+ ;(this.x = t || 0), (this.y = i || 0)
+ },
+ copy: function (t) {
+ return (this.x = t.x || 0), (this.y = t.y || 0), this
+ },
+ multiply: function (t, i) {
+ return (this.x *= t || 1), (this.y *= i || 1), this
+ },
+ divide: function (t, i) {
+ return (this.x /= t || 1), (this.y /= i || 1), this
+ },
+ add: function (t, i) {
+ return (this.x += t || 0), (this.y += i || 0), this
+ },
+ subtract: function (t, i) {
+ return (this.x -= t || 0), (this.y -= i || 0), this
+ },
+ clampX: function (t, i) {
+ return (this.x = Math.max(t, Math.min(this.x, i))), this
+ },
+ clampY: function (t, i) {
+ return (this.y = Math.max(t, Math.min(this.y, i))), this
+ },
+ flipX: function () {
+ return (this.x *= -1), this
+ },
+ flipY: function () {
+ return (this.y *= -1), this
+ }
+ }
+ const h = function (t) {
+ ;(this._canvas = null),
+ (this._context = null),
+ (this._sto = null),
+ (this._width = 0),
+ (this._height = 0),
+ (this._scroll = 0),
+ (this._ribbons = []),
+ (this._options = {
+ colorSaturation: '80%',
+ colorBrightness: '60%',
+ colorAlpha: 0.65,
+ colorCycleSpeed: 6,
+ verticalPosition: 'center',
+ horizontalSpeed: 150,
+ ribbonCount: 5,
+ strokeSize: 5,
+ parallaxAmount: -0.5,
+ animateSections: !0
+ }),
+ (this._onDraw = this._onDraw.bind(this)),
+ (this._onResize = this._onResize.bind(this)),
+ (this._onScroll = this._onScroll.bind(this)),
+ this.setOptions(t),
+ this.init()
+ }
+ return (
+ (h.prototype = {
+ constructor: h,
+ setOptions: function (t) {
+ if ('object' == typeof t)
+ for (const i in t)
+ t.hasOwnProperty(i) && (this._options[i] = t[i])
+ },
+ init: function () {
+ try {
+ ;(this._canvas = document.createElement('canvas')),
+ (this._canvas.style.display = 'block'),
+ (this._canvas.style.position = 'fixed'),
+ (this._canvas.style.margin = '0'),
+ (this._canvas.style.padding = '0'),
+ (this._canvas.style.border = '0'),
+ (this._canvas.style.outline = '0'),
+ (this._canvas.style.left = '0'),
+ (this._canvas.style.top = '0'),
+ (this._canvas.style.width = '100%'),
+ (this._canvas.style.height = '100%'),
+ (this._canvas.style['z-index'] = '0'),
+ this._onResize(),
+ (this._context = this._canvas.getContext('2d')),
+ this._context.clearRect(0, 0, this._width, this._height),
+ (this._context.globalAlpha = this._options.colorAlpha),
+ window.addEventListener('resize', this._onResize),
+ window.addEventListener('scroll', this._onScroll),
+ document.body.appendChild(this._canvas)
+ } catch (t) {
+ return void console.warn('Canvas Context Error: ' + t.toString())
+ }
+ this._onDraw()
+ },
+ addRibbon: function () {
+ const t = Math.round(o(1, 9)) > 5 ? 'right' : 'left'
+ let i = 1e3
+ const n = 200,
+ s = 0 - n,
+ h = this._width + n
+ let a = 0,
+ r = 0
+ const l = 'right' === t ? s : h
+ let c = Math.round(o(0, this._height))
+ ;/^(top|min)$/i.test(this._options.verticalPosition)
+ ? (c = 0 + n)
+ : /^(middle|center)$/i.test(this._options.verticalPosition)
+ ? (c = this._height / 2)
+ : /^(bottom|max)$/i.test(this._options.verticalPosition) &&
+ (c = this._height - n)
+ const p = [],
+ _ = new e(l, c),
+ d = new e(l, c)
+ let u = null,
+ b = Math.round(o(0, 360)),
+ f = 0
+ for (; !(i <= 0); ) {
+ if (
+ (i--,
+ (a = Math.round(
+ (1 * Math.random() - 0.2) * this._options.horizontalSpeed
+ )),
+ (r = Math.round(
+ (1 * Math.random() - 0.5) * (0.25 * this._height)
+ )),
+ (u = new e()),
+ u.copy(d),
+ 'right' === t)
+ ) {
+ if ((u.add(a, r), d.x >= h)) break
+ } else if ('left' === t && (u.subtract(a, r), d.x <= s)) break
+ p.push({
+ point1: new e(_.x, _.y),
+ point2: new e(d.x, d.y),
+ point3: u,
+ color: b,
+ delay: f,
+ dir: t,
+ alpha: 0,
+ phase: 0
+ }),
+ _.copy(d),
+ d.copy(u),
+ (f += 4),
+ (b += this._options.colorCycleSpeed)
+ }
+ this._ribbons.push(p)
+ },
+ _drawRibbonSection: function (t) {
+ if (t) {
+ if (t.phase >= 1 && t.alpha <= 0) return !0
+ if (t.delay <= 0) {
+ if (
+ ((t.phase += 0.02),
+ (t.alpha = 1 * Math.sin(t.phase)),
+ (t.alpha = t.alpha <= 0 ? 0 : t.alpha),
+ (t.alpha = t.alpha >= 1 ? 1 : t.alpha),
+ this._options.animateSections)
+ ) {
+ const i = 0.1 * Math.sin(1 + (t.phase * Math.PI) / 2)
+ 'right' === t.dir
+ ? (t.point1.add(i, 0),
+ t.point2.add(i, 0),
+ t.point3.add(i, 0))
+ : (t.point1.subtract(i, 0),
+ t.point2.subtract(i, 0),
+ t.point3.subtract(i, 0)),
+ t.point1.add(0, i),
+ t.point2.add(0, i),
+ t.point3.add(0, i)
+ }
+ } else t.delay -= 0.5
+ const i = this._options.colorSaturation,
+ n = this._options.colorBrightness,
+ o =
+ 'hsla(' +
+ t.color +
+ ', ' +
+ i +
+ ', ' +
+ n +
+ ', ' +
+ t.alpha +
+ ' )'
+ this._context.save(),
+ 0 !== this._options.parallaxAmount &&
+ this._context.translate(
+ 0,
+ this._scroll * this._options.parallaxAmount
+ ),
+ this._context.beginPath(),
+ this._context.moveTo(t.point1.x, t.point1.y),
+ this._context.lineTo(t.point2.x, t.point2.y),
+ this._context.lineTo(t.point3.x, t.point3.y),
+ (this._context.fillStyle = o),
+ this._context.fill(),
+ this._options.strokeSize > 0 &&
+ ((this._context.lineWidth = this._options.strokeSize),
+ (this._context.strokeStyle = o),
+ (this._context.lineCap = 'round'),
+ this._context.stroke()),
+ this._context.restore()
+ }
+ return !1
+ },
+ _onDraw: function () {
+ for (let t = 0, i = this._ribbons.length; t < i; ++t)
+ this._ribbons[t] || this._ribbons.splice(t, 1)
+ this._context.clearRect(0, 0, this._width, this._height)
+ for (let t = 0; t < this._ribbons.length; ++t) {
+ const i = this._ribbons[t],
+ n = i.length
+ let o = 0
+ for (let t = 0; t < n; ++t) this._drawRibbonSection(i[t]) && o++
+ o >= n && (this._ribbons[t] = null)
+ }
+ this._ribbons.length < this._options.ribbonCount &&
+ this.addRibbon(),
+ requestAnimationFrame(this._onDraw)
+ },
+ _onResize: function (t) {
+ const i = s(t)
+ ;(this._width = i.width),
+ (this._height = i.height),
+ this._canvas &&
+ ((this._canvas.width = this._width),
+ (this._canvas.height = this._height),
+ this._context &&
+ (this._context.globalAlpha = this._options.colorAlpha))
+ },
+ _onScroll: function (t) {
+ const i = s(t)
+ this._scroll = i.scrolly
+ }
+ }),
+ h
+ )
+ })())
+ new Ribbons({
+ colorSaturation: '60%',
+ colorBrightness: '50%',
+ colorAlpha: 0.5,
+ colorCycleSpeed: 5,
+ verticalPosition: 'random',
+ horizontalSpeed: 200,
+ ribbonCount: 3,
+ strokeSize: 0,
+ parallaxAmount: -0.2,
+ animateSections: !0
+ })
+}
diff --git a/components/Nest.js b/components/Nest.js
new file mode 100644
index 00000000..61f0ce30
--- /dev/null
+++ b/components/Nest.js
@@ -0,0 +1,113 @@
+/* eslint-disable */
+import React from 'react'
+
+export const Nest = () => {
+ React.useEffect(() => {
+ createNest()
+ }, [])
+}
+
+/**
+ * 创建连接点
+ * @param config
+ */
+function createNest() {
+ const e = document.getElementById('__next')
+ if(!e) return
+ function n(e, n, t) {
+ return e.getAttribute(n) || t
+ }
+ function t() {
+ ;(u = i.width =
+ window.innerWidth ||
+ document.documentElement.clientWidth ||
+ document.body.clientWidth),
+ (d = i.height =
+ window.innerHeight ||
+ document.documentElement.clientHeight ||
+ document.body.clientHeight)
+ }
+ function o() {
+ c.clearRect(0, 0, u, d)
+ const e = [s].concat(x)
+ let n, t, i, l, r, w
+ x.forEach(function (o) {
+ for (
+ o.x += o.xa,
+ o.y += o.ya,
+ o.xa *= o.x > u || o.x < 0 ? -1 : 1,
+ o.ya *= o.y > d || o.y < 0 ? -1 : 1,
+ c.fillRect(o.x - 0.5, o.y - 0.5, 1, 1),
+ t = 0;
+ t < e.length;
+ t++
+ )
+ (n = e[t]),
+ o !== n &&
+ null !== n.x &&
+ null !== n.y &&
+ ((l = o.x - n.x),
+ (r = o.y - n.y),
+ (w = l * l + r * r),
+ w < n.max &&
+ (n === s &&
+ w >= n.max / 2 &&
+ ((o.x -= 0.03 * l), (o.y -= 0.03 * r)),
+ (i = (n.max - w) / n.max),
+ c.beginPath(),
+ (c.lineWidth = i / 2),
+ (c.strokeStyle = 'rgba(' + a.c + ',' + (i + 0.2) + ')'),
+ c.moveTo(o.x, o.y),
+ c.lineTo(n.x, n.y),
+ c.stroke()))
+ e.splice(e.indexOf(o), 1)
+ }),
+ m(o)
+ }
+ var i = document.createElement('canvas')
+ i.id = 'canvasNestCreated'
+ var a = (function () {
+ const t = e
+ return {
+ z: n(t, 'zIndex', 0),
+ o: n(t, 'opacity', 0.7),
+ c: n(t, 'color', '0,0,0'),
+ n: n(t, 'count', 99)
+ }
+ })(),
+ c = i.getContext('2d')
+ let u, d
+ var m =
+ window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (e) {
+ window.setTimeout(e, 1e3 / 45)
+ }
+ const l = Math.random
+ var r,
+ s = { x: null, y: null, max: 2e4 }
+ ;(i.style.cssText =
+ 'position:fixed;top:0;left:0;z-index:' + a.z + ';opacity:' + a.o),
+ (r = 'body'), e.appendChild(i),
+ t(),
+ (window.onresize = t),
+ (window.onmousemove = function (e) {
+ ;(e = e || window.event), (s.x = e.clientX), (s.y = e.clientY)
+ }),
+ (window.onmouseout = function () {
+ ;(s.x = null), (s.y = null)
+ })
+ for (var x = [], w = 0; a.n > w; w++) {
+ const e = l() * u,
+ n = l() * d,
+ t = 2 * l() - 1,
+ o = 2 * l() - 1
+ x.push({ x: e, y: n, xa: t, ya: o, max: 6e3 })
+ }
+ setTimeout(function () {
+ o()
+ }, 100)
+}
diff --git a/components/Ribbon.js b/components/Ribbon.js
new file mode 100644
index 00000000..d96e3591
--- /dev/null
+++ b/components/Ribbon.js
@@ -0,0 +1,85 @@
+/* eslint-disable */
+import React from 'react'
+
+export const Ribbon = () => {
+ React.useEffect(() => {
+ createRibbon()
+ }, [])
+}
+
+/**
+ * 创建连接点
+ * @param config
+ */
+function createRibbon() {
+ !(function () {
+ const t = document.getElementById('__next')
+ const e = {
+ z: n(t, 'zIndex', 0),
+ a: n(t, 'alpha', 0.6),
+ s: n(t, 'size', 90),
+ c: t.getAttribute('data-click')
+ }
+ function n(t, e, n) {
+ return Number(t.getAttribute(e)) || n
+ }
+ const i = document.createElement('canvas'),
+ o = i.getContext('2d'),
+ c = window.devicePixelRatio || 1,
+ a = window.innerWidth,
+ l = window.innerHeight,
+ d = e.s
+ let r, s
+ const u = Math
+ let h = 0
+ const g = 2 * u.PI,
+ f = u.cos,
+ m = u.random
+ function x() {
+ for (
+ o.clearRect(0, 0, a, l),
+ r = [
+ { x: 0, y: 0.7 * l + d },
+ { x: 0, y: 0.7 * l - d }
+ ];
+ r[1].x < a + d;
+
+ )
+ y(r[0], r[1])
+ }
+ function y(t, e) {
+ o.beginPath(), o.moveTo(t.x, t.y), o.lineTo(e.x, e.y)
+ const n = e.x + (2 * m() - 0.25) * d,
+ i = b(e.y)
+ o.lineTo(n, i),
+ o.closePath(),
+ (h -= g / -50),
+ (o.fillStyle =
+ '#' +
+ (
+ ((127 * f(h) + 128) << 16) |
+ ((127 * f(h + g / 3) + 128) << 8) |
+ (127 * f(h + (g / 3) * 2) + 128)
+ ).toString(16)),
+ o.fill(),
+ (r[0] = r[1]),
+ (r[1] = { x: n, y: i })
+ }
+ function b(t) {
+ return (s = t + (2 * m() - 1.1) * d), s > l || s < 0 ? b(t) : s
+ }
+ ;(i.width = a * c),
+ (i.height = l * c),
+ o.scale(c, c),
+ (o.globalAlpha = e.a),
+ (i.style.cssText =
+ 'opacity: ' +
+ e.a +
+ ';position:fixed;top:0;left:0;z-index: ' +
+ e.z +
+ ';width:100%;height:100%;pointer-events:none;'),
+ document.getElementsByTagName('body')[0].appendChild(i),
+ 'false' !== e.c && ((document.onclick = x), (document.ontouchstart = x)),
+ x()
+ })()
+}
diff --git a/components/Sakura.js b/components/Sakura.js
index 24c55244..3f69cc25 100644
--- a/components/Sakura.js
+++ b/components/Sakura.js
@@ -1,15 +1,10 @@
/* eslint-disable */
-/**
- * https://codepen.io/juliangarnier/pen/gmOwJX
- * custom by hexo-theme-yun @YunYouJun
- */
import React from 'react'
export const Sakura = () => {
React.useEffect(() => {
createSakura({})
}, [])
- // return
}
/**
diff --git a/components/StarrySky.js b/components/StarrySky.js
index 045dd533..3a3a77d6 100644
--- a/components/StarrySky.js
+++ b/components/StarrySky.js
@@ -1,8 +1,4 @@
/* eslint-disable */
-/**
- * https://codepen.io/juliangarnier/pen/gmOwJX
- * custom by hexo-theme-yun @YunYouJun
- */
import React from 'react'
export const StarrySky = () => {
diff --git a/pages/_app.js b/pages/_app.js
index 099b8514..f0eedb2c 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -25,6 +25,9 @@ import { GlobalContextProvider } from '@/lib/global'
import { DebugPanel } from '@/components/DebugPanel'
import { ThemeSwitch } from '@/components/ThemeSwitch'
import { Fireworks } from '@/components/Fireworks'
+import { Nest } from '@/components/Nest'
+import { FlutteringRibbon } from '@/components/FlutteringRibbon'
+import { Ribbon } from '@/components/Ribbon'
import { Sakura } from '@/components/Sakura'
import { StarrySky } from '@/components/StarrySky'
import MusicPlayer from '@/components/MusicPlayer'
@@ -53,6 +56,9 @@ const MyApp = ({ Component, pageProps }) => {
{JSON.parse(BLOG.SAKURA) &&