<canvas></canvas>
body {
margin: 0;
overflow: hidden;
}
canvas {
image-rendering: pixelated;
}
class EventEmitter {
constructor() {
this.handlers = new Map();
}
on(event, callback) {
const handlers = this.handlers.has(event)
? this.handlers.get(event)
: new Set();
handlers.add(callback);
this.handlers.set(event, handlers);
}
emit(event, args) {
if (this.handlers.has(event)) {
const handlers = this.handlers.get(event);
for (const handler of handlers.values()) {
handler(args);
}
}
}
}
class Component {
constructor(context) {
this.canvas = context.canvas;
this.context = context;
this.events = new EventEmitter();
}
on(event, callback) {
this.events.on(event, callback);
}
emit(event, args) {
this.events.emit(event, args);
}
}
class Button extends Component {
constructor({
context,
text = 'Button',
x = 0,
y = 0,
paddingVertical = 8,
paddingHorizontal = 16,
fontSize = 16,
fontFamily = 'Segoe UI',
background = '#000000',
color = '#ffffff'
}) {
super(context);
this.text = text;
this.x = x;
this.y = y;
this.paddingVertical = paddingVertical;
this.paddingHorizontal = paddingHorizontal;
this.fontSize = fontSize;
this.fontFamily = fontFamily;
this.background = background;
this.color = color;
this.shape = new Path2D();
this.box = {
width: null,
height: null
};
this.create();
}
create() {
this.context.save();
this.context.font = `${this.fontSize}px ${this.fontFamily}`;
[this.box.width, this.box.height] = [this.context.measureText(this.text).width, this.fontSize];
this.shape.rect(this.x, this.y, this.box.width + this.paddingHorizontal * 2, this.box.height + this.paddingVertical * 2);
this.context.restore();
}
draw() {
this.context.save();
this.context.fillStyle = this.background;
this.context.fill(this.shape);
this.context.font = `${this.fontSize}px ${this.fontFamily}`;
this.context.textAlign = 'center';
this.context.textBaseline = 'middle';
this.context.fillStyle = this.color;
this.context.fillText(
this.text,
this.x + this.box.width / 2 + this.paddingHorizontal,
this.y + this.box.height / 2 + this.paddingVertical
);
this.context.restore();
}
intersect(event, x, y) {
if (this.context.isPointInPath(this.shape, x, y)) {
this.emit(event);
}
}
}
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const button = new Button({
context,
text: 'Start',
x: 100,
y: 100
});
button.on('click', () => {
alert('clicked');
});
const update = (time) => {
context.clearRect(0, 0, canvas.width, canvas.height);
button.draw();
requestAnimationFrame(update);
};
const resize = () => {
[canvas.width, canvas.height] = [innerWidth, innerHeight];
};
const init = () => {
canvas.addEventListener('click', event => {
button.intersect('click', event.offsetX, event.offsetY);
});
resize();
requestAnimationFrame(update);
};
window.addEventListener('load', init);
window.addEventListener('resize', resize);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.