<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

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js
  2. https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.9.5/dist/alpine.js