Fundamentals 8 min read

Building a VSCode Extension that Shows Kung Fu Animation While You Code

This tutorial walks you through creating a VSCode extension that pre‑loads animation frames from a video, listens to typing events, and uses the TextEditorDecorationType API to display a fast‑paced fight animation in the editor, complete with combo counters and inactivity handling.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Building a VSCode Extension that Shows Kung Fu Animation While You Code

The article introduces a fun VSCode extension that brings the excitement of the game "Black Myth: Wukong" into your coding sessions by displaying a fight animation whenever you type.

Principle : The extension uses VSCode's TextEditorDecorationType to render frame‑by‑frame animation. Since VSCode cannot play MP4 directly, the video is first converted into an image sequence with ffmpeg, then each frame is shown as a decoration.

Convert the video with the following command:

ffmpeg -i ./wukong.mp4 -vf "fps=10,scale=300:150" -compression_level 9 ./res/frames/frame%03d.png

This extracts wukong.mp4 at 10 fps, scaling each frame to 300×150 px and storing them in res/frames.

Step 1 – Initialize the project : Create a new VSCode extension project following the official VSCode documentation.

Step 2 – Pre‑load animation frames : Load the 100 PNG frames into a cache for quick access.

const frameCache: { [key: number]: string } = {};

async function preloadFrames(extensionPath: string) {
  for (let i = 1; i <= 100; i++) {
    const frameUrl = getFrameUrl(i, extensionPath);
    frameCache[i] = frameUrl;
  }
}

The helper getFrameUrl builds a Base64 data URL for each image:

function getFrameUrl(frameNumber: number, extensionPath: string): string {
  const paddedFrameNumber = frameNumber.toString().padStart(3, '0');
  const imagePath = path.join(extensionPath, 'res', 'frames', `frame${paddedFrameNumber}.png`);
  try {
    const imageBuffer = fs.readFileSync(imagePath);
    const base64Image = imageBuffer.toString('base64');
    return `data:image/png;base64,${base64Image}`;
  } catch (error) {
    console.error(`Failed to read image: ${imagePath}`, error);
    return '';
  }
}

Step 3 – Listen for typing events : When the document changes, trigger the animation, reset an inactivity timer, and increase the combo counter.

vscode.workspace.onDidChangeTextDocument(event => {
  if (event.contentChanges.length > 0) {
    updateAnimation(event.document.uri);
    resetInactivityTimer();
    incrementComboCount();
  }
});

Step 4 – Display the animation : Use TextEditorDecorationType to render each frame as a background image attached to the end of the last line.

let animationDecoration: vscode.TextEditorDecorationType | undefined;
let currentFrame = 1;

async function showNextFrame(editor: vscode.TextEditor) {
  if (!isAnimating) return;
  const frameUrl = frameCache[currentFrame];
  if (frameUrl) {
    if (animationDecoration) animationDecoration.dispose();
    animationDecoration = vscode.window.createTextEditorDecorationType({
      after: {
        contentText: comboCount > 0 ? `${comboCount}x` : '',
        margin: '0 0 0 1em',
        width: '200px',
        height: '100px',
        textDecoration: `
          none;
          position: absolute;
          left: -0.5rem;
          top: 2rem;
          z-index: 1;
          pointer-events: none;
          background-image: url(${frameUrl});
          background-size: 100% 100%;
          background-repeat: no-repeat;
        `
      },
      rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed
    });
    const lastLine = editor.document.lineAt(editor.document.lineCount - 1);
    const range = new vscode.Range(lastLine.range.end, lastLine.range.end);
    editor.setDecorations(animationDecoration, [{ range }]);
    currentFrame = (currentFrame % 100) + 1;
  }
}

Step 5 – Manage animation lifecycle and combo : Stop the animation after three seconds of inactivity and reset the combo counter.

let isAnimating = false;
let inactivityTimer: NodeJS.Timeout | undefined;
let comboCount = 0;

function resetInactivityTimer() {
  if (inactivityTimer) clearTimeout(inactivityTimer);
  inactivityTimer = setTimeout(() => {
    stopAnimation();
    resetComboCount();
  }, 3000);
}

function stopAnimation() {
  isAnimating = false;
  if (animationDecoration) {
    animationDecoration.dispose();
    animationDecoration = undefined;
  }
}

function incrementComboCount() {
  comboCount++;
  showNextFrame(vscode.window.activeTextEditor!);
}

function resetComboCount() {
  comboCount = 0;
  showNextFrame(vscode.window.activeTextEditor!);
}

Step 6 – Activate the extension : Wire the animation logic into the extension's activation entry point.

import * as vscode from 'vscode';
import { activateFightAnimation } from './fightAnimation';

export const activate = async (context: vscode.ExtensionContext) => {
  try {
    console.log('"VSCode Funny" extension activated!');
    activateFightAnimation(context);
  } catch (err) {
    console.warn('Extension activation failed', err);
  }
};

Final steps : Open the project folder in VSCode, run npm install, press F5 to launch the debug host, and start typing to see the animation in action.

The author also notes a deliberate bug that misplaces the animation, and provides a GitHub link (https://github.com/nicepkg/vscode-funny) for readers to explore the source code.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationTypeScriptExtensionVSCodeTutorialffmpeg
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.