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

kanjivg-js

v1.1.5

Published

TypeScript/React library for searching and animating kanji

Readme

kanjivg-js

TypeScript/React library for searching and animating kanji characters using the KanjiVG dataset. This library was based off the python library kanjiVG, and built to support similar functionality in a webapp environment.

A demo of the library can be found at: https://kanji-companion.com/kanjivg_js

This library was created using largely a spec-driven-development approach. The spec files produced during development of this library can be found under /SDD and include:

  • initial requirements (project-requirements.md)
  • phase 1 investigation
  • phase 2 planning
  • phase 3 implementation
  • and final requirements review

Installation

npm install kanjivg-js
yarn add kanjivg-js

Setup for Web Applications

Using the Vite Plugin (Recommended)

Add to vite.config.js:

import { kvgJs } from 'kanjivg-js/vite-plugin';

export default defineConfig({
  plugins: [
    react(),
    kvgJs(), // Automatically copies files to public/kanji/
  ],
});

Using the Webpack Plugin (Next.js supported)

Add to webpack.config.js:

const KvgJsPlugin = require('kanjivg-js/webpack-plugin');

module.exports = {
  plugins: [
    new KvgJsPlugin(),
  ],
};

Usage

API Reference

KanjiVG Methods

getKanji(kanji: string): Promise<KanjiData[]>

Get kanji data by character or unicode.

import { KanjiVG } from 'kanjivg-js'

const data = await kv.getKanji('車');
// Returns array with base kanji and any variants

searchRadical(radical: string): Promise<string[]>

Get a list of kanji characters that contain a specific radical. This returns a string list of kanji with the radical.

const chars = await kv.searchRadical('女');
// Later, when the user selects one:
const [kanjiData] = await kv.getKanji(chars[0]);

getRandom(): Promise<KanjiData>

Get a random kanji.

const randomKanji = await kv.getRandom();

KanjiData Interface

The KanjiData interface represents a complete kanji character with all its structural and visual information:

interface KanjiData {
  // Core identifiers
  character: string;           // The kanji character (e.g., "車")
  unicode: string;             // Unicode codepoint (e.g., "08eca")
  variant?: string;            // Variant identifier if present (e.g., "Kaisho")
  isVariant: boolean;          // True if this is a variant form
  
  // Structure
  strokes: StrokeData[];        // All strokes in order
  groups: GroupData[];         // Group hierarchy for radical identification
  radicalInfo?: RadicalInfo;   // Radical information if available
  
  // Metadata
  strokeCount: number;         // Total number of strokes
  components?: string[];       // Component decomposition
}

StrokeData

Each stroke contains the visual path and metadata:

interface StrokeData {
  strokeNumber: number;        // 1-based stroke order
  path: string;               // SVG path data (d attribute)
  strokeType: string;         // kvg:type (e.g., "㇐", "㇑")
  numberPosition?: {          // Position for stroke number annotation
    x: number;
    y: number;
  };
  groupId?: string;          // Which group this stroke belongs to
  isRadicalStroke?: boolean;  // True if part of a radical group
}

GroupData

Groups represent the hierarchical structure of kanji components:

interface GroupData {
  id: string;                 // Group identifier
  element?: string;           // Component kanji (e.g., "木")
  radical?: string;           // Radical type (e.g., "general")
  position?: string;          // Spatial position (e.g., "left", "right")
  childStrokes: number[];     // Array of stroke numbers in this group
  children: GroupData[];      // Sub-groups
}

RadicalInfo

Information about the kanji's radical:

interface RadicalInfo {
  radical: string;            // The radical character
  positions: string[];        // Where the radical appears (e.g., ["left"])
  strokeRanges: number[][];   // Which strokes are part of the radical
}

KanjiCard Component

Basic Example

import { KanjiVG } from 'kanjivg-js';
import { KanjiCard } from 'kanjivg-js/react';

// Create instance - initialization is automatic!
const kv = new KanjiVG();

// Get a random kanji
const kanji = await kv.getRandom();

// Render with animation
<KanjiCard 
  kanji={kanji} 
  infoPanel={{
    showInfo: true,
    location: 'bottom', // default
    style: { backgroundColor: '#f0f0f0', borderRadius: '8px' }
  }}
  animationOptions={{
    strokeSpeed: 1200, // pixels per second (uniform speed)
    strokeDelay: 500,  // delay between strokes (ms)
    showNumbers: true,
    showTrace: true,
    strokeStyling: {
      strokeColour: '#000000',
      strokeThickness: 3,
      strokeRadius: 0,
    }
  }}
/>

// Render without animation (show all strokes immediately)
<KanjiCard 
  kanji={kanji} 
  animationOptions={{
    animate: false,     // Display all strokes immediately, no animation
    showNumbers: true,
    showTrace: false,
    strokeStyling: {
      strokeColour: '#000000',
      strokeThickness: 3,
      strokeRadius: 0,
    }
  }}
/>

// Example: Color only specific radical types
<KanjiCard 
  kanji={kanji} 
  animationOptions={{
    strokeSpeed: 1200,
    strokeDelay: 500,
    showNumbers: true,
    strokeStyling: {
      strokeColour: '#000000',
      strokeThickness: 3,
      strokeRadius: 0,
    },
    radicalStyling: {
      radicalColour: '#ff0000',  // Red for radicals
      radicalThickness: 3,
      radicalRadius: 0,
      radicalType: ['general', 'nelson'],  // Color only general and nelson radicals
    }
  }}
/>

// Example: Color only traditional radicals
<KanjiCard 
  kanji={kanji} 
  animationOptions={{
    strokeStyling: {
      strokeColour: '#000000',
      strokeThickness: 3,
      strokeRadius: 0,
    },
    radicalStyling: {
      radicalColour: '#0066cc',
      radicalThickness: 3,
      radicalRadius: 0,
      radicalType: ['tradit'],  // Color only traditional radicals
    }
  }}
/>

Animation options:

{
  // Timing
  strokeSpeed?: number;     // SVG pixels per second (uniform speed per stroke)
  strokeDelay?: number;     // Delay between strokes (ms)
  animationStartDelay?: number;  // Delay before animation starts (ms, defaults to 0)
  loop?: boolean;           // Loop animation
  animate?: boolean;         // If false, displays all strokes immediately without animation (defaults to true)
  
  // Display
  showNumbers?: boolean;    // Show stroke numbers
  showTrace?: boolean;      // Show outline trace
  
  // Styling
  strokeStyling?: {
    strokeColour: string | string[];  // Single color or array for cycling
    strokeThickness: number;
    strokeRadius: number;              // 0 = square, >0 = round
  };
  
  radicalStyling?: {
    radicalColour: string | string[];
    radicalThickness: number;
    radicalRadius: number;
    radicalType?: Array<'general' | 'nelson' | 'tradit'>;  // Filter which radical types are colored
  };
  
  traceStyling?: {
    traceColour: string;
    traceThickness: number;
    traceRadius: number;
  };
  
  numberStyling?: {
    fontColour: string;
    fontWeight: number;
    fontSize: number;
  };
}

KanjiCard Props

interface InfoPanelConfig {
  showInfo: boolean;
  style?: React.CSSProperties;
  location?: 'left' | 'right' | 'top' | 'bottom';
}

interface KanjiCardProps {
  kanji: string | KanjiData;        // Character or KanjiData object
  animationOptions?: Partial<AnimationOptions>;
  onAnimationComplete?: () => void;
  className?: string;               // CSS class for the main container
  infoPanel?: InfoPanelConfig;      // Info panel configuration
}

Info Panel Configuration:

  • showInfo: Whether to display the info panel
  • style: Inline styles for the info panel (merges with default styles)
  • location: Position of the info panel relative to the kanji ('left' | 'right' | 'top' | 'bottom')

Examples:

Basic usage with info panel on the right:

<KanjiCard 
  kanji={kanji} 
  infoPanel={{
    showInfo: true,
    location: 'right',
    style: { backgroundColor: '#f5f5f5', borderRadius: '12px' }
  }}
/>

Info panel on the left with custom styling:

<KanjiCard 
  kanji={kanji} 
  infoPanel={{
    showInfo: true,
    location: 'left',
    style: { 
      backgroundColor: '#f0f8ff', 
      borderRadius: '8px',
      padding: '1.5rem',
      boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
      minWidth: '200px'
    }
  }}
/>

Info panel on top:

<KanjiCard 
  kanji={kanji} 
  infoPanel={{
    showInfo: true,
    location: 'top',
    style: { textAlign: 'center', backgroundColor: '#f9f9f9' }
  }}
/>

License

The kanjivg-js data used by this library is licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).