// A simple utility for executing multiple tasks within one requestAnimationCallback. // Priority tasks are executed before idle tasks. The utility makes it easier to // split long-running rendering tasks into batches whose duration does not exceed // the deadline provided in the parameter. var RequestAnimationFrameManager = function(deadlineMs) { var priority = []; var idle = []; var animationFrameRequested = false; this.schedulePriorityTask = function (task) { priority.push(task); schedule(); }; this.scheduleIdleTask = function (task) { idle.push(task); schedule(); }; function schedule() { if (!animationFrameRequested) { requestAnimationFrame(processQueues); animationFrameRequested = true; } } function processQueues(now) { animationFrameRequested = false; var i, task; var started = window.performance.now(); var deadline = started + deadlineMs; var priorityTasksToRun = priority; var idleTasksToRun = idle; priority = []; idle = []; execute(priorityTasksToRun, priority); execute(idleTasksToRun, idle); if (priority.length > 0 || idle.length > 0) { schedule(); } function execute(tasks, nextTasks) { for (i = 0; i < tasks.length; i++) { task = tasks[i]; var taskStart = window.performance.now(); if (taskStart < deadline) { // Let the task know what the current deadline is. // It is the responsibility of the task to return // before the deadline passes. var completed = task(now, taskStart, deadline); if (completed) { continue; } } nextTasks.push(task); } } } }; // Allow 12ms for each frame. var rafManager = new RequestAnimationFrameManager(12); // Set up canvas var screenCanvas = document.getElementById("screen"); var width = screenCanvas.width, height = screenCanvas.height; // Initialize drawing context var screenCtx = screenCanvas.getContext("2d"); screenCtx.lineWidth = 0.5; screenCtx.textAlign = "center"; screenCtx.font = "13px sans-serif"; // Draw initial content var label = 0; draw(screenCtx, label); // Animate things. On every frame we modify the transform which is a low-cost operation. // Additionally, once in a while we redraw the contents of the canvas. Since the drawing // code respects the 12ms deadline for drawing, we shouldn't see any jank. var animationStart = window.performance.now(); var frame = 0; rafManager.schedulePriorityTask(function (now) { // Update transform var v = now - animationStart; var tx = 100 + 100 * Math.sin(4 * v / 2000); var ty = 100 + 100 * Math.cos(3 * v / 2000); var scale = 0.8 + 0.4 * (1 + Math.sin(v / 2751)); screenCanvas.style.transform = "translateX(" + tx.toFixed(2) + "px) translateY(" + ty.toFixed(2) + "px) scale(" + scale + ")"; frame++; if (frame % 100 == 0) { // Redraw the content draw(screenCtx, ++label); } // Loop the animation. return false; }); // Draws content on the canvas. function draw(ctx, it) { var i = 0; // We'll split rendering into batches, so that we don't miss frames. rafManager.scheduleIdleTask(function (now, start, deadline) { var size = 15; var totalX = Math.floor(width / size); var total = totalX * Math.floor(height / size); // Make drawing a little costly, so that it needs to be split into multiple frames. for (; i < total; i++) { var x = (i % totalX) * size; var y = Math.floor(i / totalX) * size; if (window.performance.now() >= deadline) { // Deadline exceeded, finish for now, we'll get called again in the next frame. return false; } // Draw our stuff roundRectPath(ctx, x, y, size - 2, size - 2, 1); var h = (Math.atan2(x - width / 2, y - width / 2) * 180 / Math.PI) + 180 + 5 * it; ctx.fillStyle = "hsla(" + h.toFixed(2) + ", 50%, 80%, 1.0)"; ctx.strokeStyle = "hsla(" + h.toFixed(2) + ", 50%, 50%, 1.0)"; ctx.fill(); ctx.stroke(); ctx.fillStyle = "black"; ctx.fillText((it % 10), x + (size - 2) / 2, y + size - 3); } return true; // done now function roundRectPath(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); } }); }
<body> <canvas id="screen" width="800" height="600"></canvas> </body>
canvas { width: 800px; height: 600px; }