<div class="site-wrap">
  <main class="wrapper">
    <h1>Chandler hugging album</h1>

    <div class="container">
      <div class="upload-container">
        <input type="file" accept=".png, .jpg, .jpeg" id="input-pic" aria-labelledby="input-label" />
        <label id="input-label" class="upload-label" for="input-pic">
          <span>Upload image</span>
        </label>

      </div>
      <img src="https://ik.imagekit.io/rsgqxitab/for-codepens/chandler-hugging-album.png?updatedAt=1723031615623" alt="Chandler hugging album meme" />

      <div class="canvases">
        <canvas id="canvas-chandler" width="894" height="720"></canvas>
        <canvas id="canvas-album" width="397" height="500"></canvas>
      </div>
    </div>
    <span class="visually-hidden" aria-live="polite" id="feedback"></span>
    <a hidden class="btn" href="#" download="card.png">Download</a>
  </main>
  <footer class="wrapper">
    <p>
      Inspired by
      <a href="https://kamala-holding-vinyls.glitch.me/">Kamala Holding Vinyls</a>
    </p>
  </footer>
</div>
@import url("https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
*,
*::after,
*::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html,
body {
  min-height: 100vh;
}

body {
  display: grid;
  justify-content: center;
  align-content: start;
  font-family: "Inter", sans-serif;
  text-align: center;
  color: #222;
}

.wrapper {
  padding: 4rem 2rem;
  display: grid;
  justify-items: center;
  max-width: 50rem;
}

h1 {
  margin-bottom: 1.25em;
  max-width: 25ch;
  font-size: clamp(1.2rem, 0.5rem + 5vw, 4rem);
  font-family: "Bebas Neue", "Inter", sans-serif;
  font-weight: 400;
  font-style: normal;
}

.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

.container {
  position: relative;
  display: grid;
  overflow: hidden;
  background-color: #222;
  
}

.upload-container {
  text-align: center;
  font-size: clamp(0.5rem, 0.1rem + 3vw, 2rem);
  display: grid;
  justify-content: center;
  position: absolute;
  top: 57.5%;
  left: 24.7%;
  width: 43.5%;
  min-height: 42%;
  transform: rotate(0deg) perspective(51px) rotateX(1deg) rotateY(0.5deg)
    skew(-17deg, 0deg);
  background-color: #222;
  color: #fff;
  cursor: pointer;
  height: 0;
  overflow: hidden;
}

.upload-container > * {
  grid-column: 1;
  grid-row: 1;
  cursor: pointer;
}

.upload-label {
  display: flex;
  justify-content: center;
  align-items: center;
}

.upload-label span {
  max-width: 6.5ch;
}

input {
  opacity: 0;
  align-self: center;
}

input:focus + .upload-label span {
  outline: 2px dashed currentColor;
}

img {
  z-index: 99;
}

.canvases,
.canvases > *,
img {
  grid-column: 1;
  grid-row: 1;
  pointer-events: none;
}

img,
canvas {
  max-width: 100%;
  display: block;
}

.canvases {
  display: grid;
  pointer-events: none;
}

#canvas-album {
  display: none;
}

#canvas-chandler {
  position: relative;
  z-index: 99;
}

a[download] {
  margin-top: 2rem;
  color: inherit;
  text-decoration: none;
  background: #222;
  color: #fff;
  font-weight: 700;
  padding: 1rem 2rem;
  border-radius: 17px;
}

a:focus-visible {
  outline: 2px dashed #222;
  outline-offset: 2px;
}

footer {
  color: #555555;
  font-size: 0.9em;
}
const canvasesContainer = document.querySelector(".canvases");
const canvasChandler = canvasesContainer.querySelector("#canvas-chandler");
const canvasAlbum = canvasesContainer.querySelector("#canvas-album");
const input = document.querySelector("#input-pic");
const inputLabel = document.querySelector("#input-label");
const downloadButton = document.querySelector("a[download]");
const liveRegion = document.querySelector("#feedback");

function applyTransformations(ctx, canvas) {
  ctx.translate(canvas.width / 2, canvas.height / 2);
  ctx.rotate((2 * Math.PI) / 180);
  const perspective = 390;
  const scale = 1 / (1 + perspective / canvas.width);
  ctx.scale(scale, scale);
  ctx.transform(1, 0, Math.tan((-18 * Math.PI) / 180), 1, 0, 0);
  ctx.scale(1.93, 1.23);
  ctx.translate(-canvas.width / 2, -canvas.height / 2);
}

function drawImage(albumCover) {
  return new Promise((resolve) => {
    const ctx = canvasChandler.getContext("2d");
    const { width, height } = canvasChandler;

    const pictureChandler = new Image();
    pictureChandler.crossOrigin = "anonymous";
    pictureChandler.src =
      "https://ik.imagekit.io/rsgqxitab/for-codepens/chandler-hugging-album.png?updatedAt=1723031615623";
    pictureChandler.onload = () => {
      ctx.fillStyle = "#fff";
      ctx.fillRect(0, 0, width, height);

      ctx.save();

      applyTransformations(ctx, canvasAlbum);

      ctx.drawImage(
        albumCover,
        (37 * width) / 100,
        (70.7 * height) / 100,
        canvasAlbum.width,
        canvasAlbum.height
      );
      ctx.restore();
      ctx.drawImage(pictureChandler, 0, 0);

      resolve(canvasChandler.toDataURL("image/jpeg", 0.8));
    };
  });
}

function showDownloadButton(base64) {
  const image = new Image();
  image.src = base64;

  image.onload = () => {
    downloadButton.href = base64;
    downloadButton.hidden = false;
    downloadButton.focus();
  };
}

function updateLiveRegion() {
  liveRegion.textContent =
    "Your meme has been generated and is ready to be downloaded.";
}

function handleUpload(e) {
  const file = e.target.files[0];

  const image = new Image();
  image.src = URL.createObjectURL(file);
  image.onload = async (e) => {
    const result = await drawImage(e.currentTarget);
    updateLiveRegion();
    showDownloadButton(result);
  };
}

input.addEventListener("change", handleUpload);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.