Crop an avatar
Move the picture under the circle, then take the file — round corners and all, at exactly the size you asked for.
<div class="demo demo--wide">
<label class="demo-label" for="avatar">Profile picture</label>
<input id="avatar" name="avatar" type="file">
<div class="demo-row">
<button class="demo-btn" id="take">Take the file</button>
<button class="demo-btn demo-btn--ghost" id="sample">Use a sample picture</button>
</div>
<div class="result" id="result" hidden>
<img id="out" alt="The cropped picture">
<dl class="demo-kv" id="facts"></dl>
</div>
<p class="demo-hint">Drag the picture, pinch or scroll to zoom, or tab to the circle and use the
arrow keys. The corners of the file really are transparent — the chequerboard shows through.</p>
</div>
import { createImageCropper } from 'https://www.devix.pk/cdn/oss/image-cropper@1.0.0/index.js';
const result = document.getElementById('result');
const out = document.getElementById('out');
const facts = document.getElementById('facts');
const picker = createImageCropper(document.getElementById('avatar'), {
mode: 'fixed',
aspect: 1,
round: true,
width: 256,
height: 256,
type: 'image/png',
preview: true,
rotatable: true,
});
document.getElementById('take').addEventListener('click', async () => {
const crop = picker.getCrop();
if (!crop) {
facts.innerHTML = '<div><dt>nothing yet</dt><dd>choose a picture first</dd></div>';
result.hidden = false;
return;
}
const file = await picker.toFile('avatar');
out.src = URL.createObjectURL(file);
facts.innerHTML = `
<div><dt>file</dt><dd>${file.name}</dd></div>
<div><dt>size</dt><dd>${(file.size / 1024).toFixed(1)} kB</dd></div>
<div><dt>output</dt><dd>256 × 256</dd></div>
<div><dt>crop taken</dt><dd>${crop.width} × ${crop.height}</dd></div>`;
result.hidden = false;
});
// A picture drawn here, so the demo needs nothing from your disk.
document.getElementById('sample').addEventListener('click', async () => {
const canvas = Object.assign(document.createElement('canvas'), { width: 900, height: 600 });
const ctx = canvas.getContext('2d');
const sky = ctx.createLinearGradient(0, 0, 0, 600);
sky.addColorStop(0, '#7dd3fc');
sky.addColorStop(1, '#fde68a');
ctx.fillStyle = sky;
ctx.fillRect(0, 0, 900, 600);
ctx.fillStyle = '#fbbf24';
ctx.beginPath();
ctx.arc(680, 150, 62, 0, Math.PI * 2);
ctx.fill();
for (const [x, w, h, shade] of [[120, 280, 240, '#065f46'], [380, 340, 320, '#047857'], [660, 300, 200, '#0f766e']]) {
ctx.fillStyle = shade;
ctx.beginPath();
ctx.moveTo(x - w / 2, 600);
ctx.lineTo(x, 600 - h);
ctx.lineTo(x + w / 2, 600);
ctx.closePath();
ctx.fill();
}
const blob = await new Promise((r) => canvas.toBlob(r, 'image/jpeg', 0.9));
await picker.open(new File([blob], 'mountains.jpg', { type: 'image/jpeg' }));
});
@import url('https://www.devix.pk/cdn/oss/image-cropper@1.0.0/image-cropper.min.css');
.demo-label { display: block; margin-bottom: 8px; }
.dxi { --dxi-stage-height: 260px; --dxi-shade: rgb(17 17 28 / 0.68); }
.result { display: flex; align-items: center; gap: 16px; }
.result[hidden] { display: none; }
#out {
width: 96px;
height: 96px;
border-radius: 12px;
/* The chequerboard proves the corners are transparent. */
background: repeating-conic-gradient(#d4d4d8 0% 25%, #fff 0% 50%) 50% / 12px 12px;
}
#facts { flex: 1; }