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.

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

login 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.