mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Format
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import code from '@/content/TextAnimations/AsciiText/AsciiText.vue?raw'
|
import code from '@/content/TextAnimations/AsciiText/AsciiText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const asciiText: CodeObject = {
|
export const asciiText: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/AsciiText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/AsciiText`,
|
||||||
@@ -20,4 +20,4 @@ export const asciiText: CodeObject = {
|
|||||||
import AsciiText from "./AsciiText.vue";
|
import AsciiText from "./AsciiText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import code from '@content/TextAnimations/ScrambleText/ScrambleText.vue?raw'
|
import code from '@content/TextAnimations/ScrambleText/ScrambleText.vue?raw';
|
||||||
import type { CodeObject } from '../../../types/code'
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
export const scrambleTextCode: CodeObject = {
|
export const scrambleTextCode: CodeObject = {
|
||||||
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrambleText`,
|
cli: `npx jsrepo add https://vue-bits.dev/ui/TextAnimations/ScrambleText`,
|
||||||
@@ -25,4 +25,4 @@ export const scrambleTextCode: CodeObject = {
|
|||||||
import ScrambleText from "./ScrambleText.vue";
|
import ScrambleText from "./ScrambleText.vue";
|
||||||
</script>`,
|
</script>`,
|
||||||
code
|
code
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three';
|
||||||
|
|
||||||
interface AsciiTextProps {
|
interface AsciiTextProps {
|
||||||
text?: string
|
text?: string;
|
||||||
asciiFontSize?: number
|
asciiFontSize?: number;
|
||||||
textFontSize?: number
|
textFontSize?: number;
|
||||||
textColor?: string
|
textColor?: string;
|
||||||
planeBaseHeight?: number
|
planeBaseHeight?: number;
|
||||||
enableWaves?: boolean
|
enableWaves?: boolean;
|
||||||
className?: string
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<AsciiTextProps>(), {
|
const props = withDefaults(defineProps<AsciiTextProps>(), {
|
||||||
@@ -20,7 +20,7 @@ const props = withDefaults(defineProps<AsciiTextProps>(), {
|
|||||||
planeBaseHeight: 8,
|
planeBaseHeight: 8,
|
||||||
enableWaves: true,
|
enableWaves: true,
|
||||||
className: ''
|
className: ''
|
||||||
})
|
});
|
||||||
|
|
||||||
const vertexShader = `
|
const vertexShader = `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
@@ -42,7 +42,7 @@ void main() {
|
|||||||
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
const fragmentShader = `
|
const fragmentShader = `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
@@ -61,302 +61,293 @@ void main() {
|
|||||||
float a = texture2D(uTexture, pos).a;
|
float a = texture2D(uTexture, pos).a;
|
||||||
gl_FragColor = vec4(r, g, b, a);
|
gl_FragColor = vec4(r, g, b, a);
|
||||||
}
|
}
|
||||||
`
|
`;
|
||||||
|
|
||||||
// @ts-expect-error - Adding map function to Math object
|
// @ts-expect-error - Adding map function to Math object
|
||||||
Math.map = function (n: number, start: number, stop: number, start2: number, stop2: number) {
|
Math.map = function (n: number, start: number, stop: number, start2: number, stop2: number) {
|
||||||
return ((n - start) / (stop - start)) * (stop2 - start2) + start2
|
return ((n - start) / (stop - start)) * (stop2 - start2) + start2;
|
||||||
}
|
};
|
||||||
|
|
||||||
const PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1
|
const PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
|
||||||
|
|
||||||
interface AsciiFilterOptions {
|
interface AsciiFilterOptions {
|
||||||
fontSize?: number
|
fontSize?: number;
|
||||||
fontFamily?: string
|
fontFamily?: string;
|
||||||
charset?: string
|
charset?: string;
|
||||||
invert?: boolean
|
invert?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CanvasTxtOptions {
|
interface CanvasTxtOptions {
|
||||||
fontSize?: number
|
fontSize?: number;
|
||||||
fontFamily?: string
|
fontFamily?: string;
|
||||||
color?: string
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AsciiFilter {
|
class AsciiFilter {
|
||||||
renderer: THREE.WebGLRenderer
|
renderer: THREE.WebGLRenderer;
|
||||||
domElement: HTMLDivElement
|
domElement: HTMLDivElement;
|
||||||
pre: HTMLPreElement
|
pre: HTMLPreElement;
|
||||||
canvas: HTMLCanvasElement
|
canvas: HTMLCanvasElement;
|
||||||
context: CanvasRenderingContext2D | null
|
context: CanvasRenderingContext2D | null;
|
||||||
deg: number
|
deg: number;
|
||||||
invert: boolean
|
invert: boolean;
|
||||||
fontSize: number
|
fontSize: number;
|
||||||
fontFamily: string
|
fontFamily: string;
|
||||||
charset: string
|
charset: string;
|
||||||
width: number = 0
|
width: number = 0;
|
||||||
height: number = 0
|
height: number = 0;
|
||||||
center: { x: number; y: number } = { x: 0, y: 0 }
|
center: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
mouse: { x: number; y: number } = { x: 0, y: 0 }
|
mouse: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
cols: number = 0
|
cols: number = 0;
|
||||||
rows: number = 0
|
rows: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {
|
||||||
renderer: THREE.WebGLRenderer,
|
this.renderer = renderer;
|
||||||
{ fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}
|
this.domElement = document.createElement('div');
|
||||||
) {
|
this.domElement.style.position = 'absolute';
|
||||||
this.renderer = renderer
|
this.domElement.style.top = '0';
|
||||||
this.domElement = document.createElement('div')
|
this.domElement.style.left = '0';
|
||||||
this.domElement.style.position = 'absolute'
|
this.domElement.style.width = '100%';
|
||||||
this.domElement.style.top = '0'
|
this.domElement.style.height = '100%';
|
||||||
this.domElement.style.left = '0'
|
|
||||||
this.domElement.style.width = '100%'
|
|
||||||
this.domElement.style.height = '100%'
|
|
||||||
|
|
||||||
this.pre = document.createElement('pre')
|
this.pre = document.createElement('pre');
|
||||||
this.domElement.appendChild(this.pre)
|
this.domElement.appendChild(this.pre);
|
||||||
|
|
||||||
this.canvas = document.createElement('canvas')
|
this.canvas = document.createElement('canvas');
|
||||||
this.context = this.canvas.getContext('2d')
|
this.context = this.canvas.getContext('2d');
|
||||||
this.domElement.appendChild(this.canvas)
|
this.domElement.appendChild(this.canvas);
|
||||||
|
|
||||||
this.deg = 0
|
this.deg = 0;
|
||||||
this.invert = invert ?? true
|
this.invert = invert ?? true;
|
||||||
this.fontSize = fontSize ?? 12
|
this.fontSize = fontSize ?? 12;
|
||||||
this.fontFamily = fontFamily ?? "'Courier New', monospace"
|
this.fontFamily = fontFamily ?? "'Courier New', monospace";
|
||||||
this.charset =
|
this.charset = charset ?? ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';
|
||||||
charset ??
|
|
||||||
" .'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$"
|
|
||||||
|
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
this.context.imageSmoothingEnabled = false
|
this.context.imageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onMouseMove = this.onMouseMove.bind(this)
|
this.onMouseMove = this.onMouseMove.bind(this);
|
||||||
document.addEventListener('mousemove', this.onMouseMove)
|
document.addEventListener('mousemove', this.onMouseMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(width: number, height: number) {
|
setSize(width: number, height: number) {
|
||||||
this.width = width
|
this.width = width;
|
||||||
this.height = height
|
this.height = height;
|
||||||
this.renderer.setSize(width, height)
|
this.renderer.setSize(width, height);
|
||||||
this.reset()
|
this.reset();
|
||||||
|
|
||||||
this.center = { x: width / 2, y: height / 2 }
|
this.center = { x: width / 2, y: height / 2 };
|
||||||
this.mouse = { x: this.center.x, y: this.center.y }
|
this.mouse = { x: this.center.x, y: this.center.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.context!.font = `${this.fontSize}px ${this.fontFamily}`
|
this.context!.font = `${this.fontSize}px ${this.fontFamily}`;
|
||||||
const charWidth = this.context!.measureText('A').width
|
const charWidth = this.context!.measureText('A').width;
|
||||||
|
|
||||||
this.cols = Math.floor(
|
this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));
|
||||||
this.width / (this.fontSize * (charWidth / this.fontSize))
|
this.rows = Math.floor(this.height / this.fontSize);
|
||||||
)
|
|
||||||
this.rows = Math.floor(this.height / this.fontSize)
|
|
||||||
|
|
||||||
this.canvas.width = this.cols
|
this.canvas.width = this.cols;
|
||||||
this.canvas.height = this.rows
|
this.canvas.height = this.rows;
|
||||||
this.pre.style.fontFamily = this.fontFamily
|
this.pre.style.fontFamily = this.fontFamily;
|
||||||
this.pre.style.fontSize = `${this.fontSize}px`
|
this.pre.style.fontSize = `${this.fontSize}px`;
|
||||||
this.pre.style.margin = '0'
|
this.pre.style.margin = '0';
|
||||||
this.pre.style.padding = '0'
|
this.pre.style.padding = '0';
|
||||||
this.pre.style.lineHeight = '1em'
|
this.pre.style.lineHeight = '1em';
|
||||||
this.pre.style.position = 'absolute'
|
this.pre.style.position = 'absolute';
|
||||||
this.pre.style.left = '0'
|
this.pre.style.left = '0';
|
||||||
this.pre.style.top = '0'
|
this.pre.style.top = '0';
|
||||||
this.pre.style.zIndex = '9'
|
this.pre.style.zIndex = '9';
|
||||||
this.pre.style.backgroundImage = 'radial-gradient(circle, #ff6188 0%, #fc9867 50%, #ffd866 100%)'
|
this.pre.style.backgroundImage = 'radial-gradient(circle, #ff6188 0%, #fc9867 50%, #ffd866 100%)';
|
||||||
this.pre.style.backgroundAttachment = 'fixed'
|
this.pre.style.backgroundAttachment = 'fixed';
|
||||||
this.pre.style.webkitTextFillColor = 'transparent'
|
this.pre.style.webkitTextFillColor = 'transparent';
|
||||||
this.pre.style.webkitBackgroundClip = 'text'
|
this.pre.style.webkitBackgroundClip = 'text';
|
||||||
this.pre.style.backgroundClip = 'text'
|
this.pre.style.backgroundClip = 'text';
|
||||||
this.pre.style.mixBlendMode = 'difference'
|
this.pre.style.mixBlendMode = 'difference';
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove(e: MouseEvent) {
|
onMouseMove(e: MouseEvent) {
|
||||||
this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO }
|
this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };
|
||||||
}
|
}
|
||||||
|
|
||||||
render(scene: THREE.Scene, camera: THREE.Camera) {
|
render(scene: THREE.Scene, camera: THREE.Camera) {
|
||||||
this.renderer.render(scene, camera)
|
this.renderer.render(scene, camera);
|
||||||
|
|
||||||
const w = this.canvas.width
|
const w = this.canvas.width;
|
||||||
const h = this.canvas.height
|
const h = this.canvas.height;
|
||||||
this.context!.clearRect(0, 0, w, h)
|
this.context!.clearRect(0, 0, w, h);
|
||||||
if (this.context && w && h) {
|
if (this.context && w && h) {
|
||||||
this.context.drawImage(this.renderer.domElement, 0, 0, w, h)
|
this.context.drawImage(this.renderer.domElement, 0, 0, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.asciify(this.context!, w, h)
|
this.asciify(this.context!, w, h);
|
||||||
this.hue()
|
this.hue();
|
||||||
}
|
}
|
||||||
|
|
||||||
asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
||||||
if (w && h) {
|
if (w && h) {
|
||||||
const imgData = ctx.getImageData(0, 0, w, h).data
|
const imgData = ctx.getImageData(0, 0, w, h).data;
|
||||||
let str = ''
|
let str = '';
|
||||||
for (let y = 0; y < h; y++) {
|
for (let y = 0; y < h; y++) {
|
||||||
for (let x = 0; x < w; x++) {
|
for (let x = 0; x < w; x++) {
|
||||||
const i = x * 4 + y * 4 * w
|
const i = x * 4 + y * 4 * w;
|
||||||
const [r, g, b, a] = [
|
const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];
|
||||||
imgData[i],
|
|
||||||
imgData[i + 1],
|
|
||||||
imgData[i + 2],
|
|
||||||
imgData[i + 3]
|
|
||||||
]
|
|
||||||
|
|
||||||
if (a === 0) {
|
if (a === 0) {
|
||||||
str += ' '
|
str += ' ';
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255
|
const gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;
|
||||||
let idx = Math.floor((1 - gray) * (this.charset.length - 1))
|
let idx = Math.floor((1 - gray) * (this.charset.length - 1));
|
||||||
if (this.invert) idx = this.charset.length - idx - 1
|
if (this.invert) idx = this.charset.length - idx - 1;
|
||||||
str += this.charset[idx]
|
str += this.charset[idx];
|
||||||
}
|
}
|
||||||
str += '\n'
|
str += '\n';
|
||||||
}
|
}
|
||||||
this.pre.innerHTML = str
|
this.pre.innerHTML = str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get dx() {
|
get dx() {
|
||||||
return this.mouse.x - this.center.x
|
return this.mouse.x - this.center.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
get dy() {
|
get dy() {
|
||||||
return this.mouse.y - this.center.y
|
return this.mouse.y - this.center.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
hue() {
|
hue() {
|
||||||
const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI
|
const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;
|
||||||
this.deg += (deg - this.deg) * 0.075
|
this.deg += (deg - this.deg) * 0.075;
|
||||||
this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`
|
this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
document.removeEventListener('mousemove', this.onMouseMove)
|
document.removeEventListener('mousemove', this.onMouseMove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CanvasTxt {
|
class CanvasTxt {
|
||||||
canvas: HTMLCanvasElement
|
canvas: HTMLCanvasElement;
|
||||||
context: CanvasRenderingContext2D | null
|
context: CanvasRenderingContext2D | null;
|
||||||
txt: string
|
txt: string;
|
||||||
fontSize: number
|
fontSize: number;
|
||||||
fontFamily: string
|
fontFamily: string;
|
||||||
color: string
|
color: string;
|
||||||
font: string
|
font: string;
|
||||||
|
|
||||||
constructor(
|
constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {
|
||||||
txt: string,
|
this.canvas = document.createElement('canvas');
|
||||||
{ fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}
|
this.context = this.canvas.getContext('2d');
|
||||||
) {
|
this.txt = txt;
|
||||||
this.canvas = document.createElement('canvas')
|
this.fontSize = fontSize;
|
||||||
this.context = this.canvas.getContext('2d')
|
this.fontFamily = fontFamily;
|
||||||
this.txt = txt
|
this.color = color;
|
||||||
this.fontSize = fontSize
|
this.font = `600 ${this.fontSize}px ${this.fontFamily}`;
|
||||||
this.fontFamily = fontFamily
|
|
||||||
this.color = color
|
|
||||||
this.font = `600 ${this.fontSize}px ${this.fontFamily}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resize() {
|
resize() {
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
this.context.font = this.font
|
this.context.font = this.font;
|
||||||
const metrics = this.context.measureText(this.txt)
|
const metrics = this.context.measureText(this.txt);
|
||||||
|
|
||||||
const textWidth = Math.ceil(metrics.width) + 20
|
const textWidth = Math.ceil(metrics.width) + 20;
|
||||||
const textHeight =
|
const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;
|
||||||
Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20
|
|
||||||
|
|
||||||
this.canvas.width = textWidth
|
this.canvas.width = textWidth;
|
||||||
this.canvas.height = textHeight
|
this.canvas.height = textHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.context) {
|
if (this.context) {
|
||||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
this.context.fillStyle = this.color
|
this.context.fillStyle = this.color;
|
||||||
this.context.font = this.font
|
this.context.font = this.font;
|
||||||
|
|
||||||
const metrics = this.context.measureText(this.txt)
|
const metrics = this.context.measureText(this.txt);
|
||||||
const yPos = 10 + metrics.actualBoundingBoxAscent
|
const yPos = 10 + metrics.actualBoundingBoxAscent;
|
||||||
|
|
||||||
this.context.fillText(this.txt, 10, yPos)
|
this.context.fillText(this.txt, 10, yPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
get width() {
|
||||||
return this.canvas.width
|
return this.canvas.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
get height() {
|
get height() {
|
||||||
return this.canvas.height
|
return this.canvas.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
get texture() {
|
get texture() {
|
||||||
return this.canvas
|
return this.canvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CanvAscii {
|
class CanvAscii {
|
||||||
textString: string
|
textString: string;
|
||||||
asciiFontSize: number
|
asciiFontSize: number;
|
||||||
textFontSize: number
|
textFontSize: number;
|
||||||
textColor!: string
|
textColor!: string;
|
||||||
planeBaseHeight!: number
|
planeBaseHeight!: number;
|
||||||
container!: HTMLElement
|
container!: HTMLElement;
|
||||||
width!: number
|
width!: number;
|
||||||
height!: number
|
height!: number;
|
||||||
enableWaves!: boolean
|
enableWaves!: boolean;
|
||||||
camera!: THREE.PerspectiveCamera
|
camera!: THREE.PerspectiveCamera;
|
||||||
scene!: THREE.Scene
|
scene!: THREE.Scene;
|
||||||
mouse: { x: number; y: number } = { x: 0, y: 0 }
|
mouse: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
textCanvas!: CanvasTxt
|
textCanvas!: CanvasTxt;
|
||||||
texture!: THREE.CanvasTexture
|
texture!: THREE.CanvasTexture;
|
||||||
geometry!: THREE.PlaneGeometry
|
geometry!: THREE.PlaneGeometry;
|
||||||
material!: THREE.ShaderMaterial
|
material!: THREE.ShaderMaterial;
|
||||||
mesh!: THREE.Mesh
|
mesh!: THREE.Mesh;
|
||||||
renderer!: THREE.WebGLRenderer
|
renderer!: THREE.WebGLRenderer;
|
||||||
filter!: AsciiFilter
|
filter!: AsciiFilter;
|
||||||
center: { x: number; y: number } = { x: 0, y: 0 }
|
center: { x: number; y: number } = { x: 0, y: 0 };
|
||||||
animationFrameId: number = 0
|
animationFrameId: number = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: {
|
{
|
||||||
text: string
|
text,
|
||||||
asciiFontSize: number
|
asciiFontSize,
|
||||||
textFontSize: number
|
textFontSize,
|
||||||
textColor: string
|
textColor,
|
||||||
planeBaseHeight: number
|
planeBaseHeight,
|
||||||
enableWaves: boolean
|
enableWaves
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
asciiFontSize: number;
|
||||||
|
textFontSize: number;
|
||||||
|
textColor: string;
|
||||||
|
planeBaseHeight: number;
|
||||||
|
enableWaves: boolean;
|
||||||
},
|
},
|
||||||
containerElem: HTMLElement,
|
containerElem: HTMLElement,
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number
|
||||||
) {
|
) {
|
||||||
this.textString = text
|
this.textString = text;
|
||||||
this.asciiFontSize = asciiFontSize
|
this.asciiFontSize = asciiFontSize;
|
||||||
this.textFontSize = textFontSize
|
this.textFontSize = textFontSize;
|
||||||
this.textColor = textColor
|
this.textColor = textColor;
|
||||||
this.planeBaseHeight = planeBaseHeight
|
this.planeBaseHeight = planeBaseHeight;
|
||||||
this.container = containerElem
|
this.container = containerElem;
|
||||||
this.width = width
|
this.width = width;
|
||||||
this.height = height
|
this.height = height;
|
||||||
this.enableWaves = enableWaves
|
this.enableWaves = enableWaves;
|
||||||
|
|
||||||
this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000)
|
this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);
|
||||||
this.camera.position.z = 30
|
this.camera.position.z = 30;
|
||||||
|
|
||||||
this.scene = new THREE.Scene()
|
this.scene = new THREE.Scene();
|
||||||
|
|
||||||
this.onMouseMove = this.onMouseMove.bind(this)
|
this.onMouseMove = this.onMouseMove.bind(this);
|
||||||
this.setMesh()
|
this.setMesh();
|
||||||
this.setRenderer()
|
this.setRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMesh() {
|
setMesh() {
|
||||||
@@ -364,19 +355,19 @@ class CanvAscii {
|
|||||||
fontSize: this.textFontSize,
|
fontSize: this.textFontSize,
|
||||||
fontFamily: 'IBM Plex Mono',
|
fontFamily: 'IBM Plex Mono',
|
||||||
color: this.textColor
|
color: this.textColor
|
||||||
})
|
});
|
||||||
this.textCanvas.resize()
|
this.textCanvas.resize();
|
||||||
this.textCanvas.render()
|
this.textCanvas.render();
|
||||||
|
|
||||||
this.texture = new THREE.CanvasTexture(this.textCanvas.texture)
|
this.texture = new THREE.CanvasTexture(this.textCanvas.texture);
|
||||||
this.texture.minFilter = THREE.NearestFilter
|
this.texture.minFilter = THREE.NearestFilter;
|
||||||
|
|
||||||
const textAspect = this.textCanvas.width / this.textCanvas.height
|
const textAspect = this.textCanvas.width / this.textCanvas.height;
|
||||||
const baseH = this.planeBaseHeight
|
const baseH = this.planeBaseHeight;
|
||||||
const planeW = baseH * textAspect
|
const planeW = baseH * textAspect;
|
||||||
const planeH = baseH
|
const planeH = baseH;
|
||||||
|
|
||||||
this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36)
|
this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);
|
||||||
this.material = new THREE.ShaderMaterial({
|
this.material = new THREE.ShaderMaterial({
|
||||||
vertexShader,
|
vertexShader,
|
||||||
fragmentShader,
|
fragmentShader,
|
||||||
@@ -387,121 +378,120 @@ class CanvAscii {
|
|||||||
uTexture: { value: this.texture },
|
uTexture: { value: this.texture },
|
||||||
uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }
|
uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
this.mesh = new THREE.Mesh(this.geometry, this.material)
|
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
||||||
this.scene.add(this.mesh)
|
this.scene.add(this.mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
setRenderer() {
|
setRenderer() {
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true })
|
this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });
|
||||||
this.renderer.setPixelRatio(1)
|
this.renderer.setPixelRatio(1);
|
||||||
this.renderer.setClearColor(0x000000, 0)
|
this.renderer.setClearColor(0x000000, 0);
|
||||||
|
|
||||||
this.filter = new AsciiFilter(this.renderer, {
|
this.filter = new AsciiFilter(this.renderer, {
|
||||||
fontFamily: 'IBM Plex Mono',
|
fontFamily: 'IBM Plex Mono',
|
||||||
fontSize: this.asciiFontSize,
|
fontSize: this.asciiFontSize,
|
||||||
invert: true
|
invert: true
|
||||||
})
|
});
|
||||||
|
|
||||||
this.container.appendChild(this.filter.domElement)
|
this.container.appendChild(this.filter.domElement);
|
||||||
this.setSize(this.width, this.height)
|
this.setSize(this.width, this.height);
|
||||||
|
|
||||||
this.container.addEventListener('mousemove', this.onMouseMove)
|
this.container.addEventListener('mousemove', this.onMouseMove);
|
||||||
this.container.addEventListener('touchmove', this.onMouseMove)
|
this.container.addEventListener('touchmove', this.onMouseMove);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(w: number, h: number) {
|
setSize(w: number, h: number) {
|
||||||
this.width = w
|
this.width = w;
|
||||||
this.height = h
|
this.height = h;
|
||||||
|
|
||||||
this.camera.aspect = w / h
|
this.camera.aspect = w / h;
|
||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix();
|
||||||
|
|
||||||
this.filter.setSize(w, h)
|
this.filter.setSize(w, h);
|
||||||
this.center = { x: w / 2, y: h / 2 }
|
this.center = { x: w / 2, y: h / 2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
this.animate()
|
this.animate();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove(evt: MouseEvent | TouchEvent) {
|
onMouseMove(evt: MouseEvent | TouchEvent) {
|
||||||
const e = 'touches' in evt ? evt.touches[0] : evt
|
const e = 'touches' in evt ? evt.touches[0] : evt;
|
||||||
const bounds = this.container.getBoundingClientRect()
|
const bounds = this.container.getBoundingClientRect();
|
||||||
const x = e.clientX - bounds.left
|
const x = e.clientX - bounds.left;
|
||||||
const y = e.clientY - bounds.top
|
const y = e.clientY - bounds.top;
|
||||||
this.mouse = { x, y }
|
this.mouse = { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
animate() {
|
animate() {
|
||||||
const animateFrame = () => {
|
const animateFrame = () => {
|
||||||
this.animationFrameId = requestAnimationFrame(animateFrame)
|
this.animationFrameId = requestAnimationFrame(animateFrame);
|
||||||
this.render()
|
this.render();
|
||||||
}
|
};
|
||||||
animateFrame()
|
animateFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const time = new Date().getTime() * 0.001
|
const time = new Date().getTime() * 0.001;
|
||||||
|
|
||||||
this.textCanvas.render()
|
this.textCanvas.render();
|
||||||
this.texture.needsUpdate = true
|
this.texture.needsUpdate = true;
|
||||||
|
(this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);
|
||||||
|
|
||||||
;(this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time)
|
this.updateRotation();
|
||||||
|
this.filter.render(this.scene, this.camera);
|
||||||
this.updateRotation()
|
|
||||||
this.filter.render(this.scene, this.camera)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRotation() {
|
updateRotation() {
|
||||||
// @ts-expect-error - Using custom Math.map function
|
// @ts-expect-error - Using custom Math.map function
|
||||||
const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5)
|
const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);
|
||||||
// @ts-expect-error - Using custom Math.map function
|
// @ts-expect-error - Using custom Math.map function
|
||||||
const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5)
|
const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);
|
||||||
|
|
||||||
this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05
|
this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;
|
||||||
this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05
|
this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.scene.traverse((obj) => {
|
this.scene.traverse(obj => {
|
||||||
if (obj instanceof THREE.Mesh && obj.material && obj.geometry) {
|
if (obj instanceof THREE.Mesh && obj.material && obj.geometry) {
|
||||||
if (Array.isArray(obj.material)) {
|
if (Array.isArray(obj.material)) {
|
||||||
obj.material.forEach((mat) => mat.dispose())
|
obj.material.forEach(mat => mat.dispose());
|
||||||
} else {
|
} else {
|
||||||
obj.material.dispose()
|
obj.material.dispose();
|
||||||
}
|
}
|
||||||
obj.geometry.dispose()
|
obj.geometry.dispose();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
this.scene.clear()
|
this.scene.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
cancelAnimationFrame(this.animationFrameId)
|
cancelAnimationFrame(this.animationFrameId);
|
||||||
this.filter.dispose()
|
this.filter.dispose();
|
||||||
this.container.removeChild(this.filter.domElement)
|
this.container.removeChild(this.filter.domElement);
|
||||||
this.container.removeEventListener('mousemove', this.onMouseMove)
|
this.container.removeEventListener('mousemove', this.onMouseMove);
|
||||||
this.container.removeEventListener('touchmove', this.onMouseMove)
|
this.container.removeEventListener('touchmove', this.onMouseMove);
|
||||||
this.clear()
|
this.clear();
|
||||||
this.renderer.dispose()
|
this.renderer.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerRef = ref<HTMLDivElement | null>(null)
|
const containerRef = ref<HTMLDivElement | null>(null);
|
||||||
let asciiRef: CanvAscii | null = null
|
let asciiRef: CanvAscii | null = null;
|
||||||
|
|
||||||
const initializeAscii = () => {
|
const initializeAscii = () => {
|
||||||
if (!containerRef.value) return
|
if (!containerRef.value) return;
|
||||||
|
|
||||||
const { width, height } = containerRef.value.getBoundingClientRect()
|
const { width, height } = containerRef.value.getBoundingClientRect();
|
||||||
|
|
||||||
if (width === 0 || height === 0) {
|
if (width === 0 || height === 0) {
|
||||||
const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {
|
if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {
|
||||||
const { width: w, height: h } = entry.boundingClientRect
|
const { width: w, height: h } = entry.boundingClientRect;
|
||||||
|
|
||||||
asciiRef = new CanvAscii(
|
asciiRef = new CanvAscii(
|
||||||
{
|
{
|
||||||
@@ -515,17 +505,17 @@ const initializeAscii = () => {
|
|||||||
containerRef.value!,
|
containerRef.value!,
|
||||||
w,
|
w,
|
||||||
h
|
h
|
||||||
)
|
);
|
||||||
asciiRef.load()
|
asciiRef.load();
|
||||||
|
|
||||||
observer.disconnect()
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ threshold: 0.1 }
|
{ threshold: 0.1 }
|
||||||
)
|
);
|
||||||
|
|
||||||
observer.observe(containerRef.value)
|
observer.observe(containerRef.value);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
asciiRef = new CanvAscii(
|
asciiRef = new CanvAscii(
|
||||||
@@ -540,49 +530,49 @@ const initializeAscii = () => {
|
|||||||
containerRef.value,
|
containerRef.value,
|
||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
)
|
);
|
||||||
asciiRef.load()
|
asciiRef.load();
|
||||||
|
|
||||||
const ro = new ResizeObserver((entries) => {
|
const ro = new ResizeObserver(entries => {
|
||||||
if (!entries[0] || !asciiRef) return
|
if (!entries[0] || !asciiRef) return;
|
||||||
const { width: w, height: h } = entries[0].contentRect
|
const { width: w, height: h } = entries[0].contentRect;
|
||||||
if (w > 0 && h > 0) {
|
if (w > 0 && h > 0) {
|
||||||
asciiRef.setSize(w, h)
|
asciiRef.setSize(w, h);
|
||||||
}
|
|
||||||
})
|
|
||||||
ro.observe(containerRef.value)
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
ro.observe(containerRef.value);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initializeAscii()
|
initializeAscii();
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (asciiRef) {
|
if (asciiRef) {
|
||||||
asciiRef.dispose()
|
asciiRef.dispose();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [props.text, props.asciiFontSize, props.textFontSize, props.textColor, props.planeBaseHeight, props.enableWaves],
|
() => [
|
||||||
|
props.text,
|
||||||
|
props.asciiFontSize,
|
||||||
|
props.textFontSize,
|
||||||
|
props.textColor,
|
||||||
|
props.planeBaseHeight,
|
||||||
|
props.enableWaves
|
||||||
|
],
|
||||||
() => {
|
() => {
|
||||||
if (asciiRef) {
|
if (asciiRef) {
|
||||||
asciiRef.dispose()
|
asciiRef.dispose();
|
||||||
}
|
}
|
||||||
initializeAscii()
|
initializeAscii();
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="containerRef" :class="['ascii-text-container', className]" class="absolute inset-0 w-full h-full" />
|
||||||
ref="containerRef"
|
|
||||||
:class="[
|
|
||||||
'ascii-text-container',
|
|
||||||
className
|
|
||||||
]"
|
|
||||||
class="absolute inset-0 w-full h-full"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,59 +1,59 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { gsap } from 'gsap'
|
import { gsap } from 'gsap';
|
||||||
import { SplitText } from 'gsap/SplitText'
|
import { SplitText } from 'gsap/SplitText';
|
||||||
import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin'
|
import { ScrambleTextPlugin } from 'gsap/ScrambleTextPlugin';
|
||||||
|
|
||||||
gsap.registerPlugin(SplitText, ScrambleTextPlugin)
|
gsap.registerPlugin(SplitText, ScrambleTextPlugin);
|
||||||
|
|
||||||
interface ScrambleTextProps {
|
interface ScrambleTextProps {
|
||||||
radius?: number
|
radius?: number;
|
||||||
duration?: number
|
duration?: number;
|
||||||
speed?: number
|
speed?: number;
|
||||||
scrambleChars?: string
|
scrambleChars?: string;
|
||||||
className?: string
|
className?: string;
|
||||||
style?: Record<string, string | number>
|
style?: Record<string, string | number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ScrambleTextProps>(), {
|
const props = withDefaults(defineProps<ScrambleTextProps>(), {
|
||||||
radius: 100,
|
radius: 100,
|
||||||
duration: 1.2,
|
duration: 1.2,
|
||||||
speed: 0.5,
|
speed: 0.5,
|
||||||
scrambleChars: ".:",
|
scrambleChars: '.:',
|
||||||
className: "",
|
className: '',
|
||||||
style: () => ({})
|
style: () => ({})
|
||||||
})
|
});
|
||||||
|
|
||||||
const rootRef = ref<HTMLDivElement | null>(null)
|
const rootRef = ref<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
let splitText: SplitText | null = null
|
let splitText: SplitText | null = null;
|
||||||
let handleMove: ((e: PointerEvent) => void) | null = null
|
let handleMove: ((e: PointerEvent) => void) | null = null;
|
||||||
|
|
||||||
const initializeScrambleText = () => {
|
const initializeScrambleText = () => {
|
||||||
if (!rootRef.value) return
|
if (!rootRef.value) return;
|
||||||
|
|
||||||
const pElement = rootRef.value.querySelector('p')
|
const pElement = rootRef.value.querySelector('p');
|
||||||
if (!pElement) return
|
if (!pElement) return;
|
||||||
|
|
||||||
splitText = new SplitText(pElement, {
|
splitText = new SplitText(pElement, {
|
||||||
type: 'chars',
|
type: 'chars',
|
||||||
charsClass: 'inline-block will-change-transform'
|
charsClass: 'inline-block will-change-transform'
|
||||||
})
|
});
|
||||||
|
|
||||||
splitText.chars.forEach((el) => {
|
splitText.chars.forEach(el => {
|
||||||
const c = el as HTMLElement
|
const c = el as HTMLElement;
|
||||||
gsap.set(c, { attr: { 'data-content': c.innerHTML } })
|
gsap.set(c, { attr: { 'data-content': c.innerHTML } });
|
||||||
})
|
});
|
||||||
|
|
||||||
handleMove = (e: PointerEvent) => {
|
handleMove = (e: PointerEvent) => {
|
||||||
if (!splitText) return
|
if (!splitText) return;
|
||||||
|
|
||||||
splitText.chars.forEach((el) => {
|
splitText.chars.forEach(el => {
|
||||||
const c = el as HTMLElement
|
const c = el as HTMLElement;
|
||||||
const { left, top, width, height } = c.getBoundingClientRect()
|
const { left, top, width, height } = c.getBoundingClientRect();
|
||||||
const dx = e.clientX - (left + width / 2)
|
const dx = e.clientX - (left + width / 2);
|
||||||
const dy = e.clientY - (top + height / 2)
|
const dy = e.clientY - (top + height / 2);
|
||||||
const dist = Math.hypot(dx, dy)
|
const dist = Math.hypot(dx, dy);
|
||||||
|
|
||||||
if (dist < props.radius) {
|
if (dist < props.radius) {
|
||||||
gsap.to(c, {
|
gsap.to(c, {
|
||||||
@@ -65,48 +65,41 @@ const initializeScrambleText = () => {
|
|||||||
speed: props.speed
|
speed: props.speed
|
||||||
},
|
},
|
||||||
ease: 'none'
|
ease: 'none'
|
||||||
})
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
rootRef.value.addEventListener('pointermove', handleMove)
|
rootRef.value.addEventListener('pointermove', handleMove);
|
||||||
}
|
};
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (rootRef.value && handleMove) {
|
if (rootRef.value && handleMove) {
|
||||||
rootRef.value.removeEventListener('pointermove', handleMove)
|
rootRef.value.removeEventListener('pointermove', handleMove);
|
||||||
}
|
}
|
||||||
if (splitText) {
|
if (splitText) {
|
||||||
splitText.revert()
|
splitText.revert();
|
||||||
splitText = null
|
splitText = null;
|
||||||
}
|
|
||||||
handleMove = null
|
|
||||||
}
|
}
|
||||||
|
handleMove = null;
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initializeScrambleText()
|
initializeScrambleText();
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
cleanup()
|
cleanup();
|
||||||
})
|
});
|
||||||
|
|
||||||
watch(
|
watch([() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars], () => {
|
||||||
[() => props.radius, () => props.duration, () => props.speed, () => props.scrambleChars],
|
cleanup();
|
||||||
() => {
|
initializeScrambleText();
|
||||||
cleanup()
|
});
|
||||||
initializeScrambleText()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="rootRef" :class="`scramble-text ${className}`" :style="style">
|
||||||
ref="rootRef"
|
|
||||||
:class="`scramble-text ${className}`"
|
|
||||||
:style="style"
|
|
||||||
>
|
|
||||||
<p>
|
<p>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -57,11 +57,7 @@
|
|||||||
@update:model-value="forceRerender"
|
@update:model-value="forceRerender"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PreviewSwitch
|
<PreviewSwitch title="Enable Waves" v-model="enableWaves" @update:model-value="forceRerender" />
|
||||||
title="Enable Waves"
|
|
||||||
v-model="enableWaves"
|
|
||||||
@update:model-value="forceRerender"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="flex gap-4 flex-wrap">
|
<div class="flex gap-4 flex-wrap">
|
||||||
<button
|
<button
|
||||||
@@ -92,28 +88,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue';
|
||||||
import TabbedLayout from '@/components/common/TabbedLayout.vue'
|
import TabbedLayout from '@/components/common/TabbedLayout.vue';
|
||||||
import RefreshButton from '@/components/common/RefreshButton.vue'
|
import RefreshButton from '@/components/common/RefreshButton.vue';
|
||||||
import PropTable from '@/components/common/PropTable.vue'
|
import PropTable from '@/components/common/PropTable.vue';
|
||||||
import Dependencies from '@/components/code/Dependencies.vue'
|
import Dependencies from '@/components/code/Dependencies.vue';
|
||||||
import CliInstallation from '@/components/code/CliInstallation.vue'
|
import CliInstallation from '@/components/code/CliInstallation.vue';
|
||||||
import CodeExample from '@/components/code/CodeExample.vue'
|
import CodeExample from '@/components/code/CodeExample.vue';
|
||||||
import Customize from '@/components/common/Customize.vue'
|
import Customize from '@/components/common/Customize.vue';
|
||||||
import PreviewSlider from '@/components/common/PreviewSlider.vue'
|
import PreviewSlider from '@/components/common/PreviewSlider.vue';
|
||||||
import PreviewSwitch from '@/components/common/PreviewSwitch.vue'
|
import PreviewSwitch from '@/components/common/PreviewSwitch.vue';
|
||||||
import AsciiText from '@/content/TextAnimations/AsciiText/AsciiText.vue'
|
import AsciiText from '@/content/TextAnimations/AsciiText/AsciiText.vue';
|
||||||
import { asciiText } from '@/constants/code/TextAnimations/asciiTextCode'
|
import { asciiText } from '@/constants/code/TextAnimations/asciiTextCode';
|
||||||
import { useForceRerender } from '@/composables/useForceRerender'
|
import { useForceRerender } from '@/composables/useForceRerender';
|
||||||
|
|
||||||
const text = ref('Hey!')
|
const text = ref('Hey!');
|
||||||
const asciiFontSize = ref(8)
|
const asciiFontSize = ref(8);
|
||||||
const textFontSize = ref(200)
|
const textFontSize = ref(200);
|
||||||
const textColor = ref('#fdf9f3')
|
const textColor = ref('#fdf9f3');
|
||||||
const planeBaseHeight = ref(8)
|
const planeBaseHeight = ref(8);
|
||||||
const enableWaves = ref(true)
|
const enableWaves = ref(true);
|
||||||
|
|
||||||
const { rerenderKey, forceRerender } = useForceRerender()
|
const { rerenderKey, forceRerender } = useForceRerender();
|
||||||
|
|
||||||
// Color options for easy selection
|
// Color options for easy selection
|
||||||
const colorOptions = [
|
const colorOptions = [
|
||||||
@@ -125,12 +121,12 @@ const colorOptions = [
|
|||||||
{ name: 'Purple', value: '#9b59b6' },
|
{ name: 'Purple', value: '#9b59b6' },
|
||||||
{ name: 'Orange', value: '#f39c12' },
|
{ name: 'Orange', value: '#f39c12' },
|
||||||
{ name: 'Pink', value: '#e91e63' }
|
{ name: 'Pink', value: '#e91e63' }
|
||||||
]
|
];
|
||||||
|
|
||||||
const changeColor = (color: string) => {
|
const changeColor = (color: string) => {
|
||||||
textColor.value = color
|
textColor.value = color;
|
||||||
forceRerender()
|
forceRerender();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Component properties documentation
|
// Component properties documentation
|
||||||
const propData = [
|
const propData = [
|
||||||
@@ -176,5 +172,5 @@ const propData = [
|
|||||||
default: '""',
|
default: '""',
|
||||||
description: 'Additional CSS classes to apply to the component.'
|
description: 'Additional CSS classes to apply to the component.'
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,58 +1,58 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue';
|
||||||
import TabbedLayout from '../../components/common/TabbedLayout.vue'
|
import TabbedLayout from '../../components/common/TabbedLayout.vue';
|
||||||
import PropTable from '../../components/common/PropTable.vue'
|
import PropTable from '../../components/common/PropTable.vue';
|
||||||
import Dependencies from '../../components/code/Dependencies.vue'
|
import Dependencies from '../../components/code/Dependencies.vue';
|
||||||
import CliInstallation from '../../components/code/CliInstallation.vue'
|
import CliInstallation from '../../components/code/CliInstallation.vue';
|
||||||
import CodeExample from '../../components/code/CodeExample.vue'
|
import CodeExample from '../../components/code/CodeExample.vue';
|
||||||
import Customize from '../../components/common/Customize.vue'
|
import Customize from '../../components/common/Customize.vue';
|
||||||
import PreviewSlider from '../../components/common/PreviewSlider.vue'
|
import PreviewSlider from '../../components/common/PreviewSlider.vue';
|
||||||
import ScrambleText from '../../content/TextAnimations/ScrambleText/ScrambleText.vue'
|
import ScrambleText from '../../content/TextAnimations/ScrambleText/ScrambleText.vue';
|
||||||
import { scrambleTextCode } from '@/constants/code/TextAnimations/scrambleTextCode'
|
import { scrambleTextCode } from '@/constants/code/TextAnimations/scrambleTextCode';
|
||||||
|
|
||||||
const radius = ref(100)
|
const radius = ref(100);
|
||||||
const duration = ref(1.2)
|
const duration = ref(1.2);
|
||||||
const speed = ref(0.5)
|
const speed = ref(0.5);
|
||||||
const scrambleChars = ref(".:")
|
const scrambleChars = ref('.:');
|
||||||
|
|
||||||
const propData = [
|
const propData = [
|
||||||
{
|
{
|
||||||
name: "radius",
|
name: 'radius',
|
||||||
type: "number",
|
type: 'number',
|
||||||
default: "100",
|
default: '100',
|
||||||
description: "The radius around the mouse pointer within which characters will scramble."
|
description: 'The radius around the mouse pointer within which characters will scramble.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "duration",
|
name: 'duration',
|
||||||
type: "number",
|
type: 'number',
|
||||||
default: "1.2",
|
default: '1.2',
|
||||||
description: "The duration of the scramble effect on a character."
|
description: 'The duration of the scramble effect on a character.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "speed",
|
name: 'speed',
|
||||||
type: "number",
|
type: 'number',
|
||||||
default: "0.5",
|
default: '0.5',
|
||||||
description: "The speed of the scramble animation."
|
description: 'The speed of the scramble animation.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "scrambleChars",
|
name: 'scrambleChars',
|
||||||
type: "string",
|
type: 'string',
|
||||||
default: "'.:'",
|
default: "'.:'",
|
||||||
description: "The characters used for scrambling."
|
description: 'The characters used for scrambling.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "className",
|
name: 'className',
|
||||||
type: "string",
|
type: 'string',
|
||||||
default: '""',
|
default: '""',
|
||||||
description: "Additional CSS classes for the component."
|
description: 'Additional CSS classes for the component.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "style",
|
name: 'style',
|
||||||
type: "Record<string, string | number>",
|
type: 'Record<string, string | number>',
|
||||||
default: "{}",
|
default: '{}',
|
||||||
description: "Inline styles for the component."
|
description: 'Inline styles for the component.'
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -67,7 +67,8 @@ const propData = [
|
|||||||
:speed="speed"
|
:speed="speed"
|
||||||
:scrambleChars="scrambleChars"
|
:scrambleChars="scrambleChars"
|
||||||
>
|
>
|
||||||
Once you hover over me, you will see the effect in action! You can customize the radius, duration, and speed of the scramble effect.
|
Once you hover over me, you will see the effect in action! You can customize the radius, duration, and speed
|
||||||
|
of the scramble effect.
|
||||||
</ScrambleText>
|
</ScrambleText>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -83,29 +84,11 @@ const propData = [
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PreviewSlider
|
<PreviewSlider title="Radius" v-model="radius" :min="10" :max="300" :step="10" />
|
||||||
title="Radius"
|
|
||||||
v-model="radius"
|
|
||||||
:min="10"
|
|
||||||
:max="300"
|
|
||||||
:step="10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PreviewSlider
|
<PreviewSlider title="Duration" v-model="duration" :min="0.1" :max="5" :step="0.1" />
|
||||||
title="Duration"
|
|
||||||
v-model="duration"
|
|
||||||
:min="0.1"
|
|
||||||
:max="5"
|
|
||||||
:step="0.1"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PreviewSlider
|
<PreviewSlider title="Speed" v-model="speed" :min="0.1" :max="2" :step="0.1" />
|
||||||
title="Speed"
|
|
||||||
v-model="speed"
|
|
||||||
:min="0.1"
|
|
||||||
:max="2"
|
|
||||||
:step="0.1"
|
|
||||||
/>
|
|
||||||
</Customize>
|
</Customize>
|
||||||
|
|
||||||
<PropTable :data="propData" />
|
<PropTable :data="propData" />
|
||||||
|
|||||||
Reference in New Issue
Block a user