<main x-data='timer()' x-init='init()'>
<div>
<canvas id="canvas" width="300" height="300"></canvas>
</div>
<div class="button-container">
<button x-text='interval ? "Pause" : "Start"' @click='interval ? pause() : start()'>Start</button>
<button @click='stop()'>Reset</button>
</div>
<p class="error" x-show="!!error" x-html="error"></p>
</main>
body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: #263238;
}
main {
}
.button-container {
margin: 0 auto;
text-align: center;
}
.button-container button {
padding: 12px 40px;
background-color: #ffffff;
border: 0;
box-shadow: none;
margin: 0 20px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
cursor: pointer;
&:first-child {
$btn-bg: #304FFE;
background-color: darken($btn-bg, 2%);
color: #FFFFFF;
&:hover {
background-color: $btn-bg;
}
}
&:last-child {
border: 1px solid #FFFFFF;
color: #FFFFFF;
background-color: transparent;
}
}
.error {
position: absolute;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
}
View Compiled
const canvasEl = document.getElementById("canvas");
const canvasCtx = canvasEl.getContext('2d');
const zeroPad = (num, places) => String(num).padStart(places, '0');
function formatTime({minutes, seconds}) {
return `${zeroPad(minutes, 2)} : ${zeroPad(seconds, 2)}`;
}
function calculateTime({minutes, seconds}) {
if(minutes === 0 && seconds === 0) {
return {
minutes: 0,
seconds: 0,
};
} else if(seconds === 0) {
return {
minutes: minutes - 1,
seconds: 59
}
} else {
return {
minutes: minutes,
seconds: seconds - 1,
};
}
}
function writeToCanvas(text) {
canvasCtx.font = '52px serif';
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
canvasCtx.textAlign = 'center';
canvasCtx.fillStyle = "#59FFA0";
canvasCtx.fillText(text, canvas.width / 2, canvas.height / 2);
}
async function createVideo() {
const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();
video.addEventListener('loadedmetadata', () => {
video.requestPictureInPicture();
});
}
function timer() {
return {
interval: null,
error: null,
time: {
minutes: 25,
seconds: 0,
},
init() {
const text = formatTime(this.time);
return () => {
writeToCanvas(text);
try {
createVideo();
} catch(err) {
this.err = err.message;
}
}
},
start() {
this.interval = setInterval(() => {
const time = calculateTime(this.time);
this.time = time;
if(time.minutes === 0 && time.seconds === 0) {
this.stop();
}
const text = formatTime(time);
writeToCanvas(text);
}, 1000);
},
stop() {
clearInterval(this.interval);
this.interval = null;
this.time = {
minutes: 25,
seconds: 0,
}
const text = formatTime(this.time);
writeToCanvas(text);
},
pause() {
clearInterval(this.interval);
this.interval = null;
}
}
}
View Compiled
This Pen doesn't use any external CSS resources.