Skip to content

Instantly share code, notes, and snippets.

@MichalSieciechowicz
Forked from Daniel15/FaviconProgress.html
Last active April 23, 2025 11:16
Show Gist options
  • Save MichalSieciechowicz/52bd352454a6ff51a2797cc7157de80f to your computer and use it in GitHub Desktop.
Save MichalSieciechowicz/52bd352454a6ff51a2797cc7157de80f to your computer and use it in GitHub Desktop.
Using Favicon as progress bar
export class FaviconHelper {
private linkEl: HTMLLinkElement;
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
public constructor(
public color = '#fff',
public background = '#888',
public text: string | null = '#fff',
public shadow: string | null = '#000',
) {
this.linkEl = document.createElement('link');
this.linkEl.rel = 'icon';
document.head.appendChild(this.linkEl);
this.canvas = document.createElement('canvas');
this.canvas.width = this.canvas.height = 128;
this.context = this.canvas.getContext('2d')!;
this.linkEl.href = this.canvas.toDataURL('image/png');
}
public destroy() {
this.linkEl.remove();
this.canvas.remove();
}
public render(percent: number) {
this.clear();
this.renderChart(100, this.background);
this.renderChart(percent, this.color);
this.renderText(percent);
this.linkEl.href = this.canvas.toDataURL('image/png');
}
public renderMultiple(percents: number[], margin: number = 20) {
this.clear();
percents.forEach((percent, index) => {
if (index !== 0) {
this.renderChart(100, this.background, margin * index - 3);
}
this.renderChart(100, this.background, margin * index);
this.renderChart(percent, this.color, margin * index);
});
this.linkEl.href = this.canvas.toDataURL('image/png');
}
public clear() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
private renderChart(percent: number, background: string, margin: number = 0) {
const context = this.context;
const middle = this.canvas.width / 2;
const radius = this.canvas.width / 2 - margin;
const startAngle = - Math.PI / 2;
const endAngle = startAngle + Math.PI * 2 * percent / 100;
context.fillStyle = background;
context.beginPath();
context.moveTo(middle, middle);
context.arc(middle, middle, radius, startAngle, endAngle, false);
context.lineTo(middle, middle);
context.closePath();
context.fill();
}
private renderText(percent: number) {
if (!this.text || this.text === 'transparent') {
return;
}
const context = this.context;
const middle = this.canvas.width / 2;
const text = percent.toString();
context.font = '60px monospace';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.strokeStyle = this.shadow ?? '#000';
context.lineWidth = 4;
context.strokeText(text, middle, middle);
context.fillStyle = this.text;
context.fillText(text, middle, middle);
}
}
<!DOCTYPE html>
<html>
<head>
<title>Example Page</title>
</head>
<body>
<p>This page uses the favicon as a progress bar. Try clicking the button.</p>
<button onclick="run()">Do something</button>
<script>
var FaviconProgress = function() {
// Create <link> for favicon
this._linkEl = document.createElement('link');
this._linkEl.rel = 'icon';
document.head.appendChild(this._linkEl);
// Initialise canvas
this._canvas = document.createElement('canvas');
this._canvas.width = this._canvas.height = 32;
this._context = this._canvas.getContext('2d');
// Clear the favicon by default
this._linkEl.href = this._canvas.toDataURL('image/png');
};
FaviconProgress.prototype = {
render: function(percent) {
this.clear();
this._renderChart(percent);
this._renderText(percent);
this._linkEl.href = this._canvas.toDataURL('image/png');
},
clear: function() {
this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);
},
_renderChart: function(percent) {
var context = this._context;
var middle = this._canvas.width / 2;
// Render progress
// Start from 12 o'clock (-π/2)
var startAngle = -Math.PI / 2;
var endAngle = startAngle + Math.PI * 2 * percent / 100;
context.fillStyle = '#3B5998';
context.beginPath();
context.moveTo(middle, middle);
context.arc(
/* x */ middle,
/* y */ middle,
/* radius */ middle,
startAngle,
endAngle,
/* antiClockwise */ false
);
context.lineTo(middle, middle);
context.closePath();
context.fill();
},
_renderText: function(percent) {
var context = this._context;
var middle = this._canvas.width / 2;
var text = percent + '%';
context.font = '11px Arial';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.strokeStyle = 'black';
context.lineWidth = 4;
context.strokeText(text, middle, middle);
context.fillStyle = 'white';
context.fillText(text, middle, middle);
}
};
var favicon = new FaviconProgress();
function run() {
var progress = 0;
favicon.render(0);
var timer = window.setInterval(function() {
progress += 5;
favicon.render(progress);
if (progress >= 100) {
window.clearInterval(timer);
}
}, 150);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment