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.
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.pngThis 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.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.