feat: MFM Sparkle animation (#7813)
* Add sparkle mfm animation ✨
* Cleanup sparkle effect
+ spaces -> tabs and other codestyle
+ use proper image
+ listen for resizes
+ use font-size to determine particle size (for fun with x2/3/4 stacking)
This commit is contained in:
parent
76c5dc8999
commit
a499ad6879
5 changed files with 208 additions and 1 deletions
BIN
assets/client/sparkle-spritesheet.png
Normal file
BIN
assets/client/sparkle-spritesheet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 185 B |
|
@ -899,6 +899,8 @@ _mfm:
|
||||||
fontDescription: "Sets the font to display contents in."
|
fontDescription: "Sets the font to display contents in."
|
||||||
rainbow: "Rainbow"
|
rainbow: "Rainbow"
|
||||||
rainbowDescription: "Makes the content appear in rainbow colors."
|
rainbowDescription: "Makes the content appear in rainbow colors."
|
||||||
|
sparkle: "Sparkle"
|
||||||
|
sparkleDescription: "Infuses a sparkling animation"
|
||||||
_reversi:
|
_reversi:
|
||||||
reversi: "Reversi"
|
reversi: "Reversi"
|
||||||
gameSettings: "Game settings"
|
gameSettings: "Game settings"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { concat } from '@client/../prelude/array';
|
||||||
import MkFormula from '@client/components/formula.vue';
|
import MkFormula from '@client/components/formula.vue';
|
||||||
import MkCode from '@client/components/code.vue';
|
import MkCode from '@client/components/code.vue';
|
||||||
import MkGoogle from '@client/components/google.vue';
|
import MkGoogle from '@client/components/google.vue';
|
||||||
|
import MkSparkle from '@client/components/sparkle.vue';
|
||||||
import MkA from '@client/components/global/a.vue';
|
import MkA from '@client/components/global/a.vue';
|
||||||
import { host } from '@client/config';
|
import { host } from '@client/config';
|
||||||
|
|
||||||
|
@ -169,6 +170,19 @@ export default defineComponent({
|
||||||
style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
|
style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'sparkle': {
|
||||||
|
if (!this.$store.state.animatedMfm) {
|
||||||
|
return genEl(token.children);
|
||||||
|
}
|
||||||
|
let count = token.props.args.count ? parseInt(token.props.args.count) : 10;
|
||||||
|
if (count > 100) {
|
||||||
|
count = 100;
|
||||||
|
}
|
||||||
|
const speed = token.props.args.speed ? parseFloat(token.props.args.speed) : 1;
|
||||||
|
return h(MkSparkle, {
|
||||||
|
count, speed,
|
||||||
|
}, genEl(token.children));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (style == null) {
|
if (style == null) {
|
||||||
return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);
|
return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);
|
||||||
|
|
180
src/client/components/sparkle.vue
Normal file
180
src/client/components/sparkle.vue
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
<template>
|
||||||
|
<span class="mk-sparkle">
|
||||||
|
<span ref="content">
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
<canvas ref="canvas"></canvas>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import * as os from '@client/os';
|
||||||
|
|
||||||
|
const sprite = new Image();
|
||||||
|
sprite.src = "/static-assets/client/sparkle-spritesheet.png";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
count: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
speed: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sprites: [0,6,13,20],
|
||||||
|
particles: [],
|
||||||
|
anim: null,
|
||||||
|
ctx: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createSparkles(w, h, count) {
|
||||||
|
var holder = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
|
||||||
|
const color = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6);
|
||||||
|
|
||||||
|
holder[i] = {
|
||||||
|
position: {
|
||||||
|
x: Math.floor(Math.random() * w),
|
||||||
|
y: Math.floor(Math.random() * h)
|
||||||
|
},
|
||||||
|
style: this.sprites[ Math.floor(Math.random() * 4) ],
|
||||||
|
delta: {
|
||||||
|
x: Math.floor(Math.random() * 1000) - 500,
|
||||||
|
y: Math.floor(Math.random() * 1000) - 500
|
||||||
|
},
|
||||||
|
color: color,
|
||||||
|
opacity: Math.random(),
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return holder;
|
||||||
|
},
|
||||||
|
draw(time) {
|
||||||
|
this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
|
||||||
|
this.ctx.beginPath();
|
||||||
|
|
||||||
|
const particleSize = Math.floor(this.fontSize / 2);
|
||||||
|
this.particles.forEach((particle) => {
|
||||||
|
var modulus = Math.floor(Math.random()*7);
|
||||||
|
|
||||||
|
if (Math.floor(time) % modulus === 0) {
|
||||||
|
particle.style = this.sprites[ Math.floor(Math.random()*4) ];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.save();
|
||||||
|
this.ctx.globalAlpha = particle.opacity;
|
||||||
|
this.ctx.drawImage(sprite, particle.style, 0, 7, 7, particle.position.x, particle.position.y, particleSize, particleSize);
|
||||||
|
|
||||||
|
this.ctx.globalCompositeOperation = "source-atop";
|
||||||
|
this.ctx.globalAlpha = 0.5;
|
||||||
|
this.ctx.fillStyle = particle.color;
|
||||||
|
this.ctx.fillRect(particle.position.x, particle.position.y, particleSize, particleSize);
|
||||||
|
|
||||||
|
this.ctx.restore();
|
||||||
|
});
|
||||||
|
this.ctx.stroke();
|
||||||
|
},
|
||||||
|
tick() {
|
||||||
|
this.anim = window.requestAnimationFrame((time) => {
|
||||||
|
if (!this.$refs.canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.particles.forEach((particle) => {
|
||||||
|
if (!particle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var randX = Math.random() > Math.random() * 2;
|
||||||
|
var randY = Math.random() > Math.random() * 3;
|
||||||
|
|
||||||
|
if (randX) {
|
||||||
|
particle.position.x += (particle.delta.x * this.speed) / 1500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!randY) {
|
||||||
|
particle.position.y -= (particle.delta.y * this.speed) / 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( particle.position.x > this.$refs.canvas.width ) {
|
||||||
|
particle.position.x = -7;
|
||||||
|
} else if (particle.position.x < -7) {
|
||||||
|
particle.position.x = this.$refs.canvas.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (particle.position.y > this.$refs.canvas.height) {
|
||||||
|
particle.position.y = -7;
|
||||||
|
particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width);
|
||||||
|
} else if (particle.position.y < -7) {
|
||||||
|
particle.position.y = this.$refs.canvas.height;
|
||||||
|
particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
particle.opacity -= 0.005;
|
||||||
|
|
||||||
|
if (particle.opacity <= 0) {
|
||||||
|
particle.opacity = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.draw(time);
|
||||||
|
|
||||||
|
this.tick();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resize() {
|
||||||
|
if (this.$refs.content) {
|
||||||
|
const contentRect = this.$refs.content.getBoundingClientRect();
|
||||||
|
this.fontSize = parseFloat(getComputedStyle(this.$refs.content).fontSize);
|
||||||
|
const padding = this.fontSize * 0.2;
|
||||||
|
|
||||||
|
this.$refs.canvas.width = parseInt(contentRect.width + padding);
|
||||||
|
this.$refs.canvas.height = parseInt(contentRect.height + padding);
|
||||||
|
|
||||||
|
this.particles = this.createSparkles(this.$refs.canvas.width, this.$refs.canvas.height, this.count);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.ctx = this.$refs.canvas.getContext('2d');
|
||||||
|
|
||||||
|
new ResizeObserver(this.resize).observe(this.$refs.content);
|
||||||
|
|
||||||
|
this.resize();
|
||||||
|
this.tick();
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
this.resize();
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
window.cancelAnimationFrame(this.anim);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mk-sparkle {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
> canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: -0.1em;
|
||||||
|
left: -0.1em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -271,6 +271,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="section _block">
|
||||||
|
<div class="title">{{ $ts._mfm.sparkle }}</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>{{ $ts._mfm.sparkleDescription }}</p>
|
||||||
|
<div class="preview">
|
||||||
|
<Mfm :text="preview_sparkle"/>
|
||||||
|
<MkTextarea v-model="preview_sparkle"><span>MFM</span></MkTextarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -294,7 +304,7 @@ export default defineComponent({
|
||||||
preview_hashtag: '#test',
|
preview_hashtag: '#test',
|
||||||
preview_url: `https://example.com`,
|
preview_url: `https://example.com`,
|
||||||
preview_link: `[${this.$ts._mfm.dummy}](https://example.com)`,
|
preview_link: `[${this.$ts._mfm.dummy}](https://example.com)`,
|
||||||
preview_emoji: `:${this.$instance.emojis[0].name}:`,
|
preview_emoji: this.$instance.emojis.length ? `:${this.$instance.emojis[0].name}:` : `:emojiname:`,
|
||||||
preview_bold: `**${this.$ts._mfm.dummy}**`,
|
preview_bold: `**${this.$ts._mfm.dummy}**`,
|
||||||
preview_small: `<small>${this.$ts._mfm.dummy}</small>`,
|
preview_small: `<small>${this.$ts._mfm.dummy}</small>`,
|
||||||
preview_center: `<center>${this.$ts._mfm.dummy}</center>`,
|
preview_center: `<center>${this.$ts._mfm.dummy}</center>`,
|
||||||
|
@ -317,6 +327,7 @@ export default defineComponent({
|
||||||
preview_x4: `$[x4 🍮]`,
|
preview_x4: `$[x4 🍮]`,
|
||||||
preview_blur: `$[blur ${this.$ts._mfm.dummy}]`,
|
preview_blur: `$[blur ${this.$ts._mfm.dummy}]`,
|
||||||
preview_rainbow: `$[rainbow 🍮]`,
|
preview_rainbow: `$[rainbow 🍮]`,
|
||||||
|
preview_sparkle: `$[sparkle 🍮]`,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue