You can record a canvas element into a video without requiring any screen-recording permissions from the user. We use the canvas.captureStream and MediaRecorder API to do that.

Setup

Our example has a canvas, two buttons (to start and stop the recording) and a status message. We record the video of canvas and then download it once the recording is finished.

let mediaRecorder = null;
let recordedChunks = [];
let isRecording = false;

const canvas = document.getElementById('gameCanvas');
const buttonStartRecording = document.getElementById('startRecord');
const buttonStopRecording = document.getElementById('stopRecord');
const status = document.getElementById('status');

Start recording

We get the video stream from a canvas with canvas.captureStream and specify our desired FPS there. Then we feed it to the MediaRecorder instance. Lastly, when the media-recorder receives the stop command, it will start downloading the recorded video.

const startRecording = async () => {
	try {
		// Request a stream at 30 FPS
		const stream = canvas.captureStream(30);
		
		mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
		recordedChunks = [];
		
		mediaRecorder.addEventListener('dataavailable', (event) => {
			if (event.data.size > 0) {
				recordedChunks.push(event.data);
			}
		});
		
		mediaRecorder.addEventListener('stop', () => {
			downloadRecording();
		});
		
		mediaRecorder.start();
		isRecording = true;
		
		updateUi();
	} catch (error) {
		console.error('Error starting recording:', error);
		alert('Error starting recording:', error.message);
	}
};

buttonStartRecording.addEventListener('click', startRecording);

Stopping the recording

We simply ask the mediaRecorder to stop recording, and it fires the stop event on itself which then triggers the download.

const stopRecording = () => {
	if (mediaRecorder && isRecording) {
		mediaRecorder.stop();
		isRecording = false;
		updateUi();
	}
};

buttonStopRecording.addEventListener('click', stopRecording);

Downloading the video

We add an invisible <a> element and then click it.

const downloadRecording = () => {
	if (recordedChunks.length === 0) {
		alert('No recording data available!');
		return;
	}
	
	const blob = new Blob(recordedChunks, { type: 'video/webm' });
	
	const url = URL.createObjectURL(blob);
	const a = document.createElement('a');
	a.style.display = 'none';
	a.href = url;
	
	a.download = `canvas-recording-${Date.now()}.webm`;
	
	document.body.appendChild(a);
	a.click();
	
	setTimeout(() => {
		document.body.removeChild(a);
		URL.revokeObjectURL(url);
	}, 100);
	
	recordedChunks = [];
};

Sample

Here's a sample demo to test: