@rlawncks125/component-render-ani

Reusable Vue canvas animation component.
2,393
Install
npm install @rlawncks125/component-render-ani
Latest Version:0.1.13
Vue:3
Last Updated:Jun 21, 2026
Links: npm
Maintainer: rlawncks125

Component Render Ani

Reusable Vue canvas animation component.

@rlawncks125/component-render-ani exposes RenderAniCanvas, a canvas-like component that watches the #render slot and animates newly added Vue components onto the screen.

Install

npm install @rlawncks125/component-render-ani

Nuxt Usage

Add the Nuxt module to nuxt.config.ts.

export default defineNuxtConfig({
  modules: ["@rlawncks125/component-render-ani/nuxt"],
});

Nuxt can then auto-import the component:

<RenderAniCanvas />

The Nuxt module also registers the package stylesheet automatically.

Vue Usage

Register the plugin once in your Vue app entry.

import { createApp } from "vue";
import ComponentRenderAni from "@rlawncks125/component-render-ani";
import App from "./App.vue";

createApp(App).use(ComponentRenderAni).mount("#app");

After registration, Vue/Volar can discover the global component:

<RenderAniCanvas />

If you import the component directly instead of using app.use, import it like this:

import { RenderAniCanvas } from "@rlawncks125/component-render-ani";

Basic Example

Write ordinary Vue components or DOM directly in #render. RenderAniCanvas observes its top-level VNodes, measures their real DOM, and animates newly added nodes. Your component data and internal DOM structure stay entirely yours.

<script setup lang="ts">
import { ref } from "vue";
import { RenderAniCanvas } from "@rlawncks125/component-render-ani";
import MyCard from "./MyCard.vue";

const cards = ref([
  {
    id: "first-card",
    title: "First rendered card",
    description: "This component is rendered through the canvas animation.",
  },
]);

const addCard = () => {
  const nextNumber = cards.value.length + 1;

  cards.value.push({
    id: `card-${nextNumber}`,
    title: `Rendered card ${nextNumber}`,
    description: "Appending ordinary Vue content triggers the draw sequence.",
  });
};
</script>

<template>
  <RenderAniCanvas>
    <template #controls="{ running, queue }">
      <button type="button" :disabled="running" @click="addCard">
        Add card
      </button>

      <span v-if="running || queue.length">
        {{ running ? "Drawing" : "" }}
        {{ queue.length ? `${queue.length} waiting` : "" }}
      </span>
    </template>

    <template #render>
      <MyCard
        v-for="card in cards"
        :key="card.id"
        class="custom-card"
        :description="card.description"
        :title="card.title"
      />
    </template>
  </RenderAniCanvas>
</template>

Headless Styling

The package exports CSS at @rlawncks125/component-render-ani/style.css.

If your bundler does not automatically include CSS from the package entry, import it once in your app entry file.

import "@rlawncks125/component-render-ani/style.css";

RenderAniCanvas is mostly headless. It keeps only the classes needed for measurement, scrolling, column span, and animation. Build the visible layout with classes.wrap and classes.render. The internal canvas owns scrolling, so you usually do not need to set overflow classes yourself.

<RenderAniCanvas
  :classes="{
    wrap: 'h-screen w-screen bg-white',
    header: 'px-5 pt-5',
    render: 'mx-auto grid w-[min(960px,calc(100vw-32px))] grid-cols-12 gap-4 py-8',
    item: 'rounded-lg',
  }"
/>

Cursor

Use the cursor prop to change the built-in cursor. Supported types are pointer, dot, ring, image, and none.

<RenderAniCanvas
  :cursor="{
    type: 'ring',
    size: 28,
    class: 'text-blue-500',
  }"
/>

Use an image cursor with imageSrc.

<RenderAniCanvas
  :cursor="{
    type: 'image',
    imageSrc: '/cursor.png',
    size: 36,
    offsetX: -4,
    offsetY: -4,
  }"
/>

For a fully custom cursor, use the #cursor slot.

<RenderAniCanvas>
  <template #cursor="{ clicking }">
    <div
      class="grid size-8 place-items-center rounded-full bg-black text-white"
      :class="{ 'scale-90': clicking }"
    >
      AI
    </div>
  </template>
</RenderAniCanvas>

Animation Types

Use animation.type to choose how a component is drawn. Supported types are draw, instant, spotlight, scan, and typewriter.

<RenderAniCanvas
  :animation="{
    type: 'spotlight',
    speed: 1.4,
  }"
/>
<RenderAniCanvas :animation="{ type: 'scan' }" />
<RenderAniCanvas :animation="{ type: 'typewriter' }" />

Internal Structure

RenderAniCanvas renders the user-provided #render components into this internal structure:

wrap
├─ header
│  └─ #controls slot
└─ canvas frame
   ├─ canvas
   │  └─ render
   │     └─ item
   │        └─ rendered component
   └─ overlay
      ├─ selection
      └─ cursor

The same names are used by the classes and styles props. In most cases, customizing wrap and render is enough.

<RenderAniCanvas
  :classes="{
    wrap: 'h-screen w-screen bg-white',
    header: 'px-5 pt-5',
    render: 'mx-auto grid w-[min(960px,calc(100vw-32px))] grid-cols-12 gap-4 py-8',
    item: 'rounded-lg',
  }"
/>
  • wrap: root wrapper for the whole canvas component.
  • header: wrapper around the #controls slot.
  • canvas: internal scroll container.
  • render: render area and layout container.
  • item: required render item section for span, measurement, ref, and animation.

Every internal element also includes a debug attribute so it is easy to inspect in DevTools:

[data-render-ani-el="wrap"]
[data-render-ani-el="header"]
[data-render-ani-el="canvas"]
[data-render-ani-el="render"]
[data-render-ani-el="item"]
[data-render-ani-el="overlay"]
[data-render-ani-el="selection"]
[data-render-ani-el="spotlight"]
[data-render-ani-el="scan"]
[data-render-ani-el="typewriter"]
[data-render-ani-el="cursor"]
[data-render-ani-el="measure"]

Rendered items also include internal data-render-ani-* attributes for debugging.

Props

<RenderAniCanvas
  :animation="{ speed: 1.6 }"
  :auto-run="true"
  :classes="{
    wrap: 'h-screen w-screen',
    header: 'px-5 pt-5',
    render: 'grid grid-cols-12 gap-4 p-6',
    item: 'rounded-lg',
  }"
  :default-heights="{ MyCard: 240 }"
  :fallback-height="180"
  :styles="{
    wrap: { minHeight: '100vh' },
    header: { pointerEvents: 'auto' },
    render: { paddingTop: '120px' },
  }"
/>
  • animation: controls animation speed and behavior.
  • autoRun: watches #render slot changes and draws automatically. Default: true.
  • defaultHeights: initial reserved height by componentName.
  • fallbackHeight: reserved height when no default height is found. Default: 180.
  • rootAttrs: pass-through attrs for the root wrapper.
  • canvasAttrs: pass-through attrs for the scroll canvas.
  • renderAreaAttrs: pass-through attrs for the render container.
  • renderItemAttrs: pass-through attrs for each rendered item.
  • classes: Tailwind/class customization map for wrap, header, canvas, render, and item.
  • styles: style customization map for wrap, header, canvas, render, and item.

Default utility classes are intentionally minimal and are merged with tailwind-merge, so your custom classes win when they conflict with built-in classes.

Animation

Use animation.speed to make every built-in animation faster or slower. The default speed is 1. Higher values are faster.

<RenderAniCanvas :animation="{ speed: 2 }" />

Use animation.type to choose the animation behavior. Current options are "draw" and "instant". The "draw" type is the default canvas drawing animation. The "instant" type keeps the same render flow, but skips motion durations.

<RenderAniCanvas :animation="{ type: 'instant' }" />

You can also override individual durations in milliseconds. These values are still divided by speed.

<RenderAniCanvas
  :animation="{
    type: 'draw',
    speed: 1.4,
    durations: {
      cursorMove: 260,
      cursorClick: 80,
      draw: 420,
      mountDelay: 80,
      reveal: 180,
      scroll: 220,
    },
  }"
/>

Slots

#render

Put any Vue component or native DOM structure that should be drawn here. Each top-level VNode is one animation unit; everything below it keeps its original DOM hierarchy, attributes, props, and slots. Use stable Vue keys when nodes are added dynamically so append-only changes can be detected reliably.

<template #render>
  <HeroSection key="hero" />

  <section key="content" class="content">
    <h2>Any DOM structure</h2>
    <MyCard />
  </section>
</template>

#controls

Optional. Use this slot for buttons, status labels, or custom controls.

<template #controls="{ running, queue, resetAll, syncSlottedComponents }">
  <button type="button" :disabled="running" @click="resetAll">
    Reset
  </button>

  <button type="button" :disabled="running" @click="syncSlottedComponents">
    Sync
  </button>

  <span>{{ queue.length }} waiting</span>
</template>

Slot props:

interface RenderAniCanvasSlotProps {
  addComponent: (definition: ComponentDefinition) => Promise<void>;
  queue: DrawItem[];
  resetAll: () => void;
  runComponents: (definitions: ComponentDefinition[]) => Promise<void>;
  runSlottedComponents: () => Promise<void>;
  running: boolean;
  slots: DrawSlot[];
  syncSlottedComponents: () => Promise<void>;
}

Build And Publish

bun install
bun run build:lib
npm publish --access public

Related Packages

@rlawncks125/otel-sdk

이 저장소의 Collector 규약에 맞춘 단일 패키지입니다. 기본 SDK와 Elysia/Nuxt adap...

575
@rlawncks125/otel-web

Zero-config Nuxt module for the Frontend Observability Web SDK

558

Version History

Version Released Vue License
0.1.13 ^3.5.0
0.1.12 ^3.5.0
0.1.11 ^3.5.0
0.1.9 ^3.5.0
0.1.8 ^3.5.0