How to Save Canvas Animation as Gif or Webm

How to save canvas animation as gif or webm?

In modern browsers you can use a conjunction of the MediaRecorder API and the HTMLCanvasElement.captureStream method.

The MediaRecorder API will be able to encode a MediaStream in a video or audio media file on the fly, resulting in far less memory needed than when you grab still images.

const ctx = canvas.getContext('2d');var x = 0;anim();startRecording();
function startRecording() { const chunks = []; // here we will store our recorded media chunks (Blobs) const stream = canvas.captureStream(); // grab our canvas MediaStream const rec = new MediaRecorder(stream); // init the recorder // every time the recorder has new data, we will store it in our array rec.ondataavailable = e => chunks.push(e.data); // only when the recorder stops, we construct a complete Blob from all the chunks rec.onstop = e => exportVid(new Blob(chunks, {type: 'video/webm'})); rec.start(); setTimeout(()=>rec.stop(), 3000); // stop recording in 3s}
function exportVid(blob) { const vid = document.createElement('video'); vid.src = URL.createObjectURL(blob); vid.controls = true; document.body.appendChild(vid); const a = document.createElement('a'); a.download = 'myvid.webm'; a.href = vid.src; a.textContent = 'download the video'; document.body.appendChild(a);}
function anim(){ x = (x + 1) % canvas.width; ctx.fillStyle = 'white'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.fillStyle = 'black'; ctx.fillRect(x - 20, 0, 40, 40); requestAnimationFrame(anim);}
<canvas id="canvas"></canvas>

How to record an HTML animation and save it as a video, in an automated manner in the backend

Late answer from someone looking for similar options due to the convenience of some browser SVG APIs:

My first recommendation, as someone who has written a fair amount of my own audio visualization software, is to use a graphics library and language that don't require a browser or GPU, like Gd or Anti-grain Geometry or Cairo with any server-side language. You might also check out Processing.org (which I haven't used), not sure if there's a headless version.

If that's not possible, I've found these so far but haven't tried them:

  • https://github.com/tungs/timecut

  • https://github.com/myplanet/headless-render

  • https://wave.video/blog/how-we-render-animated-content-from-html5-canvas/

Gif not playing over canvas animation on Firefox

This is indeed a recent (one week) regression, apparently caused by this commit.

I did open https://bugzilla.mozilla.org/1692736, let's hope they can fix it before it lands on stable branch.

You can workaround this bug either by disabling WebRender in your own Firefox by going to about:config and then toggle gfx.webrender.force-disabled to false, or by forcing a re-rendering of the element where the gif is rendered, e.g through a minimal opacity variation in a CSS animation:

const separation = 100, amountX = 70, amountY = 50;
let container, camera, scene, renderer, particles;
let count = 0, windowHalfX = window.innerWidth / 2, windowHalfY = window.innerHeight / 2, cameraPosition = 80;

const init = () => {
container = document.createElement('div');
document.body.appendChild(container);

camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.x = cameraPosition;
camera.position.y = 1000;
camera.position.z = -550;
camera.zoom = 1.2;

scene = new THREE.Scene();

const numParticles = amountX * amountY;
const positions = new Float32Array(numParticles * 3);
const scales = new Float32Array(numParticles);

let i = 0, j = 0;

for(let ix = 0; ix < amountX; ix++) {
for(let iy = 0; iy < amountY; iy++) {
positions[i] = ix * separation - ((amountX * separation ) / 2);
positions[i + 1] = 0;
positions[i + 2] = iy * separation - ((amountY * separation ) / 2);

scales[j] = 1;

i += 3;
j ++;
}
}

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));

const material = new THREE.ShaderMaterial({
uniforms: {
color: {value: new THREE.Color(0xeae6c3)},
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
});

particles = new THREE.Points(geometry, material);
scene.add(particles);

renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setClearColor(0x192735, 1);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
container.style.touchAction = 'none';

window.addEventListener( 'resize', onWindowResize );
};

const onWindowResize = () => {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );
};

const render = () => {
camera.lookAt(scene.position);

const positions = particles.geometry.attributes.position.array;
const scales = particles.geometry.attributes.scale.array;

let i = 0, j = 0;

for (let ix = 0; ix < amountX; ix ++) {
for (let iy = 0; iy < amountY; iy ++) {
positions[i + 1] = (Math.sin((ix + count) * 0.3) * 50) + (Math.sin((iy + count) * 0.5) * 50);

scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 7 + (Math.sin((iy + count) * 0.5) + 1) * 7;

i += 3;
j ++;
}
}

particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;

renderer.render(scene, camera);

count += 0.011;
};

const animate = () => {
requestAnimationFrame(animate);

render();
};

init();
animate();

const moveCamera = () => {
cameraPosition = 150;
};
/*
we animate a very small opacity variation
to force rerendering of the gif image
*/
@keyframes bug1692736 { to { opacity: 0.051; } }
body::after {
animation: bug1692736 10s infinite;
content: '';
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
background-image: url(https://res.cloudinary.com/axiol/image/upload/v1612477975/CodePen/noise.gif);
opacity: 0.05;
pointer-events: none;
}

canvas {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
z-index: -1;
}
<script src="https://unpkg.com/three@0.125.2/build/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float scale;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = scale * (300.0 / - mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
</script>

<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
void main() {
if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.475 ) discard;
gl_FragColor = vec4(color, 1.0);
}
</script>


Related Topics



Leave a reply



Submit