<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);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.