npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rlawncks125/component-render-ani

v0.1.11

Published

Reusable Vue canvas animation component.

Downloads

1,756

Readme

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 both component names:

<RenderAniCanvas />
<RenderAiCanvas />

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 these global components:

<RenderAniCanvas />
<RenderAiCanvas />

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

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

Basic Example

Add components to renderLists. When the list changes, RenderAniCanvas reads the #render slot and draws the new component.

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

const renderLists = ref<RenderAniCanvasItem[]>([
  {
    id: "first-card",
    slotId: "first-card",
    span: "half",
    component: MyCard,
    componentName: "MyCard",
    attrs: {
      class: "custom-card",
      style: { borderColor: "#4285f4" },
    },
    props: {
      title: "First rendered card",
      description: "This component is rendered through the canvas animation.",
    },
  },
]);

const addCard = () => {
  const id = `card-${renderLists.value.length + 1}`;

  renderLists.value.push({
    id,
    slotId: id,
    span: "half",
    component: MyCard,
    componentName: "MyCard",
    props: {
      title: `Rendered card ${renderLists.value.length + 1}`,
      description: "Pushing to renderLists triggers the canvas 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>
      <template v-for="item in renderLists" :key="item.id">
        <component
          :is="item.component"
          :component-name="item.componentName"
          :slot-id="item.slotId"
          :span="item.span"
          v-bind="{ ...(item.attrs || {}), ...(item.props || {}) }"
        />
      </template>
    </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 data-render-ani-slot-id, data-render-ani-span, and data-render-ani-component.

Render Item

interface RenderAniCanvasItem {
  attrs?: Record<string, unknown>;
  id: string;
  slotId?: string;
  span?: "half" | "full";
  component: string | Component;
  componentName?: string;
  props?: Record<string, unknown>;
}
  • id: stable key for Vue v-for.
  • attrs: pass-through attrs such as class, style, data-*, and aria-*.
  • slotId: canvas slot identity. If omitted, the VNode key or fallback order is used.
  • span: "half" renders in a 6-column slot, "full" renders full-width.
  • component: Vue component object or registered component name.
  • componentName: optional name used for default height lookup.
  • props: props passed to the rendered component.

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

Required for list-driven rendering. Put the components that should be drawn here.

<template #render>
  <template v-for="item in renderLists" :key="item.id">
    <component
      :is="item.component"
      :component-name="item.componentName"
      :slot-id="item.slotId"
      :span="item.span"
      v-bind="{ ...(item.attrs || {}), ...(item.props || {}) }"
    />
  </template>
</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

See USAGE.md for the longer local development notes.