mirror of
https://github.com/DavidHDev/vue-bits.git
synced 2026-03-07 14:39:30 -07:00
Merge pull request #20 from Utkarsh-Singhal-26/feat/meta-balls
Added <MetaBalls /> animation
This commit is contained in:
@@ -45,6 +45,7 @@ export const CATEGORIES = [
|
|||||||
'Magnet',
|
'Magnet',
|
||||||
'Cubes',
|
'Cubes',
|
||||||
'Blob Cursor',
|
'Blob Cursor',
|
||||||
|
'Meta Balls',
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const animations = {
|
|||||||
'splash-cursor': () => import('../demo/Animations/SplashCursorDemo.vue'),
|
'splash-cursor': () => import('../demo/Animations/SplashCursorDemo.vue'),
|
||||||
'noise': () => import('../demo/Animations/NoiseDemo.vue'),
|
'noise': () => import('../demo/Animations/NoiseDemo.vue'),
|
||||||
'blob-cursor': () => import('../demo/Animations/BlobCursorDemo.vue'),
|
'blob-cursor': () => import('../demo/Animations/BlobCursorDemo.vue'),
|
||||||
|
'meta-balls': () => import('../demo/Animations/MetaBallsDemo.vue'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const textAnimations = {
|
const textAnimations = {
|
||||||
|
|||||||
26
src/constants/code/Animations/metaBallsCode.ts
Normal file
26
src/constants/code/Animations/metaBallsCode.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import code from '@/content/Animations/MetaBalls/MetaBalls.vue?raw';
|
||||||
|
import type { CodeObject } from '../../../types/code';
|
||||||
|
|
||||||
|
export const metaBalls: CodeObject = {
|
||||||
|
cli: `npx jsrepo add https://vue-bits.dev/ui/Animations/MetaBalls`,
|
||||||
|
installation: `npm i ogl`,
|
||||||
|
usage: `<template>
|
||||||
|
<MetaBalls
|
||||||
|
color="color"
|
||||||
|
cursorBallColor="cursorBallColor"
|
||||||
|
:cursorBallSize="cursorBallSize"
|
||||||
|
:ballCount="ballCount"
|
||||||
|
:animationSize="animationSize"
|
||||||
|
:enableMouseInteraction="enableMouseInteraction"
|
||||||
|
:enableTransparency="true"
|
||||||
|
:hoverSmoothness="hoverSmoothness"
|
||||||
|
:clumpFactor="clumpFactor"
|
||||||
|
:speed="speed"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MetaBalls from "./MetaBalls.vue";
|
||||||
|
</script>`,
|
||||||
|
code
|
||||||
|
};
|
||||||
267
src/content/Animations/MetaBalls/MetaBalls.vue
Normal file
267
src/content/Animations/MetaBalls/MetaBalls.vue
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Camera, Mesh, Program, Renderer, Transform, Triangle, Vec3 } from 'ogl';
|
||||||
|
import { onMounted, onUnmounted, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
|
interface MetaBallsProps {
|
||||||
|
color?: string;
|
||||||
|
speed?: number;
|
||||||
|
enableMouseInteraction?: boolean;
|
||||||
|
hoverSmoothness?: number;
|
||||||
|
animationSize?: number;
|
||||||
|
ballCount?: number;
|
||||||
|
clumpFactor?: number;
|
||||||
|
cursorBallSize?: number;
|
||||||
|
cursorBallColor?: string;
|
||||||
|
enableTransparency?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type BallParams = {
|
||||||
|
st: number;
|
||||||
|
dtFactor: number;
|
||||||
|
baseScale: number;
|
||||||
|
toggle: number;
|
||||||
|
radius: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<MetaBallsProps>(), {
|
||||||
|
color: '#27FF64',
|
||||||
|
speed: 0.3,
|
||||||
|
enableMouseInteraction: true,
|
||||||
|
hoverSmoothness: 0.05,
|
||||||
|
animationSize: 30,
|
||||||
|
ballCount: 15,
|
||||||
|
clumpFactor: 1,
|
||||||
|
cursorBallSize: 3,
|
||||||
|
cursorBallColor: '#27FF64',
|
||||||
|
enableTransparency: false
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseHexColor(hex: string): [number, number, number] {
|
||||||
|
const c = hex.replace('#', '');
|
||||||
|
return [parseInt(c.slice(0, 2), 16) / 255, parseInt(c.slice(2, 4), 16) / 255, parseInt(c.slice(4, 6), 16) / 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
function fract(x: number) {
|
||||||
|
return x - Math.floor(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash31(p: number): number[] {
|
||||||
|
const r = [p * 0.1031, p * 0.103, p * 0.0973].map(fract);
|
||||||
|
const r_yzx = [r[1], r[2], r[0]];
|
||||||
|
const dotVal = r[0] * (r_yzx[0] + 33.33) + r[1] * (r_yzx[1] + 33.33) + r[2] * (r_yzx[2] + 33.33);
|
||||||
|
return r.map(val => fract(val + dotVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash33(v: number[]): number[] {
|
||||||
|
const p = [v[0] * 0.1031, v[1] * 0.103, v[2] * 0.0973].map(fract);
|
||||||
|
const dotVal = p[0] * (p[1] + 33.33) + p[1] * (p[2] + 33.33) + p[2] * (p[0] + 33.33);
|
||||||
|
const r = p.map(val => fract(val + dotVal));
|
||||||
|
return r.map((_, i) => fract((r[i % 3] + r[(i + 1) % 3]) * r[(i + 2) % 3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const vertex = `#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
layout(location = 0) in vec2 position;
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fragment = `#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
uniform vec3 iResolution;
|
||||||
|
uniform float iTime;
|
||||||
|
uniform vec3 iMouse;
|
||||||
|
uniform vec3 iColor;
|
||||||
|
uniform vec3 iCursorColor;
|
||||||
|
uniform float iAnimationSize;
|
||||||
|
uniform int iBallCount;
|
||||||
|
uniform float iCursorBallSize;
|
||||||
|
uniform vec3 iMetaBalls[50];
|
||||||
|
uniform float iClumpFactor;
|
||||||
|
uniform bool enableTransparency;
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
float getMetaBallValue(vec2 c, float r, vec2 p) {
|
||||||
|
vec2 d = p - c;
|
||||||
|
float dist2 = dot(d, d);
|
||||||
|
return (r * r) / dist2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fc = gl_FragCoord.xy;
|
||||||
|
float scale = iAnimationSize / iResolution.y;
|
||||||
|
vec2 coord = (fc - iResolution.xy * 0.5) * scale;
|
||||||
|
vec2 mouseW = (iMouse.xy - iResolution.xy * 0.5) * scale;
|
||||||
|
|
||||||
|
float m1 = 0.0;
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
if (i >= iBallCount) break;
|
||||||
|
m1 += getMetaBallValue(iMetaBalls[i].xy, iMetaBalls[i].z, coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float m2 = getMetaBallValue(mouseW, iCursorBallSize, coord);
|
||||||
|
float total = m1 + m2;
|
||||||
|
|
||||||
|
float f = smoothstep(-1.0, 1.0, (total - 1.3) / min(1.0, fwidth(total)));
|
||||||
|
|
||||||
|
vec3 cFinal = vec3(0.0);
|
||||||
|
if (total > 0.0) {
|
||||||
|
float alpha1 = m1 / total;
|
||||||
|
float alpha2 = m2 / total;
|
||||||
|
cFinal = iColor * alpha1 + iCursorColor * alpha2;
|
||||||
|
}
|
||||||
|
|
||||||
|
outColor = vec4(cFinal * f, enableTransparency ? f : 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const containerRef = shallowRef<HTMLDivElement>();
|
||||||
|
let cleanUpAnimation: () => void = () => {};
|
||||||
|
|
||||||
|
const setupAnimation = () => {
|
||||||
|
const container = containerRef.value;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const dpr = 1;
|
||||||
|
const renderer = new Renderer({ dpr, alpha: true, premultipliedAlpha: false });
|
||||||
|
const gl = renderer.gl;
|
||||||
|
gl.clearColor(0, 0, 0, props.enableTransparency ? 0 : 1);
|
||||||
|
container.appendChild(gl.canvas);
|
||||||
|
|
||||||
|
const camera = new Camera(gl, {
|
||||||
|
left: -1,
|
||||||
|
right: 1,
|
||||||
|
top: 1,
|
||||||
|
bottom: -1,
|
||||||
|
near: 0.1,
|
||||||
|
far: 10
|
||||||
|
});
|
||||||
|
camera.position.z = 1;
|
||||||
|
|
||||||
|
const geometry = new Triangle(gl);
|
||||||
|
const [r1, g1, b1] = parseHexColor(props.color);
|
||||||
|
const [r2, g2, b2] = parseHexColor(props.cursorBallColor);
|
||||||
|
|
||||||
|
const metaBallsUniform: Vec3[] = Array.from({ length: 50 }, () => new Vec3());
|
||||||
|
const program = new Program(gl, {
|
||||||
|
vertex,
|
||||||
|
fragment,
|
||||||
|
uniforms: {
|
||||||
|
iTime: { value: 0 },
|
||||||
|
iResolution: { value: new Vec3() },
|
||||||
|
iMouse: { value: new Vec3() },
|
||||||
|
iColor: { value: new Vec3(r1, g1, b1) },
|
||||||
|
iCursorColor: { value: new Vec3(r2, g2, b2) },
|
||||||
|
iAnimationSize: { value: props.animationSize },
|
||||||
|
iBallCount: { value: props.ballCount },
|
||||||
|
iCursorBallSize: { value: props.cursorBallSize },
|
||||||
|
iMetaBalls: { value: metaBallsUniform },
|
||||||
|
iClumpFactor: { value: props.clumpFactor },
|
||||||
|
enableTransparency: { value: props.enableTransparency }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
|
mesh.setParent(new Transform());
|
||||||
|
|
||||||
|
const effectiveBallCount = Math.min(props.ballCount, 50);
|
||||||
|
const ballParams: BallParams[] = Array.from({ length: effectiveBallCount }, (_, i) => {
|
||||||
|
const h1 = hash31(i + 1);
|
||||||
|
const h2 = hash33(h1);
|
||||||
|
return {
|
||||||
|
st: h1[0] * 2 * Math.PI,
|
||||||
|
dtFactor: 0.1 * Math.PI + h1[1] * (0.3 * Math.PI),
|
||||||
|
baseScale: 5.0 + h1[1] * 5.0,
|
||||||
|
toggle: Math.floor(h2[0] * 2),
|
||||||
|
radius: 0.5 + h2[2] * 1.5
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mouseBallPos = { x: 0, y: 0 };
|
||||||
|
let pointerInside = false,
|
||||||
|
pointerX = 0,
|
||||||
|
pointerY = 0;
|
||||||
|
|
||||||
|
function resize() {
|
||||||
|
if (!container) return;
|
||||||
|
const { clientWidth, clientHeight } = container;
|
||||||
|
renderer.setSize(clientWidth * dpr, clientHeight * dpr);
|
||||||
|
gl.canvas.style.width = `${clientWidth}px`;
|
||||||
|
gl.canvas.style.height = `${clientHeight}px`;
|
||||||
|
program.uniforms.iResolution.value.set(gl.canvas.width, gl.canvas.height, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
resize();
|
||||||
|
|
||||||
|
container.addEventListener('pointermove', e => {
|
||||||
|
if (!props.enableMouseInteraction) return;
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
pointerX = ((e.clientX - rect.left) / rect.width) * gl.canvas.width;
|
||||||
|
pointerY = (1 - (e.clientY - rect.top) / rect.height) * gl.canvas.height;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addEventListener('pointerenter', () => (pointerInside = true));
|
||||||
|
container.addEventListener('pointerleave', () => (pointerInside = false));
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
let animationFrameId: number;
|
||||||
|
|
||||||
|
function update(time: number) {
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
const elapsed = (time - startTime) * 0.001;
|
||||||
|
program.uniforms.iTime.value = elapsed;
|
||||||
|
|
||||||
|
for (let i = 0; i < effectiveBallCount; i++) {
|
||||||
|
const { st, dtFactor, baseScale, toggle, radius } = ballParams[i];
|
||||||
|
const dt = elapsed * props.speed * dtFactor;
|
||||||
|
const angle = st + dt;
|
||||||
|
const x = Math.cos(angle);
|
||||||
|
const y = Math.sin(angle + dt * toggle);
|
||||||
|
metaBallsUniform[i].set(x * baseScale * props.clumpFactor, y * baseScale * props.clumpFactor, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetX = pointerInside
|
||||||
|
? pointerX
|
||||||
|
: gl.canvas.width * 0.5 + Math.cos(elapsed * props.speed) * gl.canvas.width * 0.15;
|
||||||
|
const targetY = pointerInside
|
||||||
|
? pointerY
|
||||||
|
: gl.canvas.height * 0.5 + Math.sin(elapsed * props.speed) * gl.canvas.height * 0.15;
|
||||||
|
|
||||||
|
mouseBallPos.x += (targetX - mouseBallPos.x) * props.hoverSmoothness;
|
||||||
|
mouseBallPos.y += (targetY - mouseBallPos.y) * props.hoverSmoothness;
|
||||||
|
program.uniforms.iMouse.value.set(mouseBallPos.x, mouseBallPos.y, 0);
|
||||||
|
|
||||||
|
renderer.render({ scene: mesh.parent!, camera });
|
||||||
|
}
|
||||||
|
|
||||||
|
animationFrameId = requestAnimationFrame(update);
|
||||||
|
|
||||||
|
cleanUpAnimation = () => {
|
||||||
|
cancelAnimationFrame(animationFrameId);
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
container.removeEventListener('pointermove', () => {});
|
||||||
|
container.removeEventListener('pointerenter', () => {});
|
||||||
|
container.removeEventListener('pointerleave', () => {});
|
||||||
|
if (container.contains(gl.canvas)) container.removeChild(gl.canvas);
|
||||||
|
gl.getExtension('WEBGL_lose_context')?.loseContext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(setupAnimation);
|
||||||
|
onUnmounted(() => cleanUpAnimation?.());
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props,
|
||||||
|
() => {
|
||||||
|
cleanUpAnimation();
|
||||||
|
setupAnimation();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="relative w-full h-full" />
|
||||||
|
</template>
|
||||||
204
src/demo/Animations/MetaBallsDemo.vue
Normal file
204
src/demo/Animations/MetaBallsDemo.vue
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<TabbedLayout>
|
||||||
|
<template #preview>
|
||||||
|
<div class="relative h-[500px] overflow-hidden demo-container">
|
||||||
|
<MetaBalls
|
||||||
|
:color="color"
|
||||||
|
:cursorBallColor="color"
|
||||||
|
:cursorBallSize="cursorBallSize"
|
||||||
|
:ballCount="ballCount"
|
||||||
|
:animationSize="animationSize"
|
||||||
|
:enableMouseInteraction="enableMouseInteraction"
|
||||||
|
:hoverSmoothness="hoverSmoothness"
|
||||||
|
:clumpFactor="clumpFactor"
|
||||||
|
:speed="speed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Customize>
|
||||||
|
<PreviewColor title="Color" v-model="color" @update:model-value="forceRerender" />
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Ball Count"
|
||||||
|
:min="2"
|
||||||
|
:max="30"
|
||||||
|
:step="1"
|
||||||
|
v-model="ballCount"
|
||||||
|
@onChange="
|
||||||
|
(val: number) => {
|
||||||
|
ballCount = val;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Speed"
|
||||||
|
:min="0.1"
|
||||||
|
:max="1"
|
||||||
|
:step="0.1"
|
||||||
|
v-model="speed"
|
||||||
|
@onChange="
|
||||||
|
(val: number) => {
|
||||||
|
speed = val;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Size"
|
||||||
|
:min="10"
|
||||||
|
:max="50"
|
||||||
|
:step="1"
|
||||||
|
v-model="animationSize"
|
||||||
|
@onChange="
|
||||||
|
(val: number) => {
|
||||||
|
animationSize = val;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Clump Factor"
|
||||||
|
:min="0.1"
|
||||||
|
:max="2"
|
||||||
|
:step="0.1"
|
||||||
|
v-model="clumpFactor"
|
||||||
|
@onChange="
|
||||||
|
(val: number) => {
|
||||||
|
clumpFactor = val;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSwitch title="Follow Cursor" v-model="enableMouseInteraction" @update:model-value="forceRerender" />
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Cursor Smoothing"
|
||||||
|
:min="0.001"
|
||||||
|
:max="0.25"
|
||||||
|
:step="0.001"
|
||||||
|
v-model="hoverSmoothness"
|
||||||
|
@onChange="
|
||||||
|
(val: number) => {
|
||||||
|
hoverSmoothness = val;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PreviewSlider
|
||||||
|
title="Cursor Size"
|
||||||
|
:min="1"
|
||||||
|
:max="5"
|
||||||
|
:step="1"
|
||||||
|
v-model="cursorBallSize"
|
||||||
|
@onChange="
|
||||||
|
(val: number) => {
|
||||||
|
cursorBallSize = val;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</Customize>
|
||||||
|
|
||||||
|
<PropTable :data="propData" />
|
||||||
|
<Dependencies :dependency-list="['ogl']" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #code>
|
||||||
|
<CodeExample :code-object="metaBalls" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cli>
|
||||||
|
<CliInstallation :command="metaBalls.cli" />
|
||||||
|
</template>
|
||||||
|
</TabbedLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useForceRerender } from '@/composables/useForceRerender';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import CliInstallation from '../../components/code/CliInstallation.vue';
|
||||||
|
import CodeExample from '../../components/code/CodeExample.vue';
|
||||||
|
import Dependencies from '../../components/code/Dependencies.vue';
|
||||||
|
import Customize from '../../components/common/Customize.vue';
|
||||||
|
import PreviewColor from '../../components/common/PreviewColor.vue';
|
||||||
|
import PreviewSlider from '../../components/common/PreviewSlider.vue';
|
||||||
|
import PreviewSwitch from '../../components/common/PreviewSwitch.vue';
|
||||||
|
import PropTable from '../../components/common/PropTable.vue';
|
||||||
|
import TabbedLayout from '../../components/common/TabbedLayout.vue';
|
||||||
|
import { metaBalls } from '../../constants/code/Animations/metaBallsCode';
|
||||||
|
import MetaBalls from '../../content/Animations/MetaBalls/MetaBalls.vue';
|
||||||
|
|
||||||
|
const { forceRerender } = useForceRerender();
|
||||||
|
|
||||||
|
const color = ref('#27FF64');
|
||||||
|
const speed = ref(0.3);
|
||||||
|
const animationSize = ref(30);
|
||||||
|
const ballCount = ref(15);
|
||||||
|
const clumpFactor = ref(1);
|
||||||
|
const enableMouseInteraction = ref(true);
|
||||||
|
const hoverSmoothness = ref(0.15);
|
||||||
|
const cursorBallSize = ref(2);
|
||||||
|
|
||||||
|
const propData = [
|
||||||
|
{
|
||||||
|
name: 'color',
|
||||||
|
type: 'string',
|
||||||
|
default: '#27FF64',
|
||||||
|
description: 'The base color of the metaballs.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'speed',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.3',
|
||||||
|
description: 'Speed multiplier for the animation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableMouseInteraction',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'true',
|
||||||
|
description: 'Enables or disables the ball following the mouse.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableTransparency',
|
||||||
|
type: 'boolean',
|
||||||
|
default: 'false',
|
||||||
|
description: 'Enables or disables transparency for the container of the animation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hoverSmoothness',
|
||||||
|
type: 'number',
|
||||||
|
default: '0.05',
|
||||||
|
description: 'Smoothness factor for the cursor ball when following the mouse.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'animationSize',
|
||||||
|
type: 'number',
|
||||||
|
default: '30',
|
||||||
|
description: 'The size of the world for the animation.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ballCount',
|
||||||
|
type: 'number',
|
||||||
|
default: '15',
|
||||||
|
description: 'Number of metaballs rendered.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'clumpFactor',
|
||||||
|
type: 'number',
|
||||||
|
default: '1',
|
||||||
|
description: 'Determines how close together the balls are rendered.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cursorBallSize',
|
||||||
|
type: 'number',
|
||||||
|
default: '3',
|
||||||
|
description: 'Size of the cursor-controlled ball.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cursorBallColor',
|
||||||
|
type: 'string',
|
||||||
|
default: '#27FF64',
|
||||||
|
description: 'Color of the cursor ball.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user