JSFiddle - React, Tailwind, and code Playground

HTML

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

<a href="#" onclick="downloadCanvasImage(event)">Download</a>
</body>

<script>
function getCanvas () {
	return document.getElementById("myCanvas");
}

function drawCircle () {
	let canvas = getCanvas();
	let ctx = canvas.getContext("2d");
  
  let centerX = canvas.width / 2;
  let centerY = canvas.height / 2;

  let circleRadius = canvas.width / 2;
	
  ctx.beginPath();
	ctx.arc(centerX, centerY, circleRadius, 0, 2 * Math.PI);
  ctx.stroke();
  
  ctx.clip();
  ctx.save();
}

function drawImage (src, x = 0, y = 0) {
	let image = document.createElement('img');
  image.src = src;
  
  image.onload = function() {
    let canvas = getCanvas();
    let ctx = canvas.getContext("2d");

    let imageWidth = image.width;
    let imageHeight = image.height;
		
    ctx.drawImage(image, 0, 0, imageWidth, imageHeight, x, y, imageWidth * 0.2, imageHeight * 0.2);
    
    ctx.save();
  };
}

function drawText (text, x = 0, y = 0) {
  let canvas = getCanvas();
  let ctx = canvas.getContext("2d");
    
	ctx.fillStyle = "black";
  let font = "bold 24px 微軟正黑體, serif";
  ctx.font = font;
  ctx.textBaseline = "bottom";
  ctx.fillText(text, x, y);
  
  ctx.save();
}

function downloadCanvasImage (e) {
	e.preventDefault()

	let canvas = getCanvas();
  
  canvas.toBlob(blob => {
  	let url = URL.createObjectURL(blob)
    
    let link = document.createElement('a')
    link.innerText = 'Download'
    link.href = url
    link.download = 'output.png'

    document.body.appendChild(link)
    link.click()
    link.parentNode.removeChild(link)
  })
}

function init () {
  drawCircle()
  drawText('我是誰',25, 70)
 ...