Cropper.js Aspect Ratio Presets — Free JS Snippet
Cropper.js Aspect-Ratio Presets for Social Posts · Forms · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="sp-wrap">
<div class="sp-presets" id="spPresets" role="group" aria-label="Aspect ratio presets"></div>
<div class="sp-stage"><img id="spImg" alt="Photo to crop"></div>
<div class="sp-foot">
<div class="sp-info">
<b id="spName">Instagram square</b>
<span id="spSize">Output 1080 × 1080</span>
<span id="spSel">Selection -</span>
</div>
<button type="button" class="sp-go" id="spGo">Export crop</button>
</div>
<div class="sp-result" id="spResult" hidden><canvas id="spCanvas"></canvas><span id="spResText"></span></div>
</div>body { background: #f3f4f9; padding: 20px; font-family: system-ui, sans-serif; }
.sp-wrap { max-width: 640px; margin: 0 auto; background: #fff; border: 1px solid #dfe2ee; border-radius: 16px; padding: 16px; box-shadow: 0 8px 24px rgba(30,30,80,.06); }
.sp-presets { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.sp-presets button { display: flex; align-items: center; gap: 8px; font: 700 12.5px/1.1 system-ui, sans-serif; color: #384057; background: #f0f2f8; border: 1.5px solid transparent; border-radius: 10px; padding: 7px 11px; cursor: pointer; }
.sp-presets button:hover { background: #e6e9f4; }
.sp-presets button[aria-pressed="true"] { background: #eef0ff; border-color: #6366f1; color: #3730a3; }
.sp-box { display: inline-block; border: 2px solid currentColor; border-radius: 3px; opacity: .8; }
.sp-stage { height: 330px; background: #10141f; border-radius: 12px; overflow: hidden; }
.sp-stage img { display: block; max-width: 100%; }
.sp-foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
.sp-info { display: flex; flex-direction: column; gap: 2px; font-size: 12.5px; color: #566074; }
.sp-info b { font-size: 14px; color: #141a2e; }
.sp-go { font: 700 14px/1 system-ui, sans-serif; color: #fff; background: #4f46e5; border: 0; border-radius: 10px; padding: 12px 18px; cursor: pointer; }
.sp-go:hover { background: #4338ca; }
.sp-result { display: flex; align-items: center; gap: 12px; margin-top: 12px; padding: 10px; background: #f3f4fb; border-radius: 10px; font-size: 12.5px; color: #48506a; }
.sp-result canvas { max-height: 80px; max-width: 140px; border-radius: 6px; border: 1px solid #d7dbee; }// Each preset = a ratio plus the pixel size the platform actually wants.
const PRESETS = [
{ id: 'sq', name: 'Instagram square', ratio: 1, w: 1080, h: 1080 },
{ id: 'port', name: 'Instagram portrait', ratio: 4 / 5, w: 1080, h: 1350 },
{ id: 'yt', name: 'YouTube thumbnail', ratio: 16 / 9, w: 1280, h: 720 },
{ id: 'story', name: 'Story / Reel', ratio: 9 / 16, w: 1080, h: 1920 },
{ id: 'link', name: 'Link preview (OG)', ratio: 1.91, w: 1200, h: 630 },
{ id: 'free', name: 'Free', ratio: NaN, w: 0, h: 0 }, // NaN = no constraint
];
function makePhoto(w, h) {
const c = document.createElement('canvas'); c.width = w; c.height = h;
const x = c.getContext('2d');
const g = x.createLinearGradient(0, 0, w, h);
g.addColorStop(0, '#fbbf24'); g.addColorStop(0.5, '#f472b6'); g.addColorStop(1, '#6366f1');
x.fillStyle = g; x.fillRect(0, 0, w, h);
x.fillStyle = 'rgba(255,255,255,.92)'; x.beginPath(); x.arc(w * 0.7, h * 0.3, h * 0.1, 0, 7); x.fill();
x.fillStyle = 'rgba(24,20,70,.5)';
x.beginPath(); x.moveTo(0, h); x.lineTo(w * 0.18, h * 0.6); x.lineTo(w * 0.34, h * 0.78); x.lineTo(w * 0.56, h * 0.4); x.lineTo(w * 0.8, h * 0.76); x.lineTo(w, h * 0.58); x.lineTo(w, h); x.fill();
x.fillStyle = '#fff'; x.font = '700 ' + Math.round(h * 0.075) + 'px system-ui, sans-serif'; x.textAlign = 'center';
x.fillText('Sample photo 2400 x 1600', w / 2, h * 0.12);
return c.toDataURL('image/jpeg', 0.9);
}
const img = document.getElementById('spImg');
img.src = makePhoto(2400, 1600);
const bar = document.getElementById('spPresets');
let current = PRESETS[0];
function boxIcon(r) {
const w = isNaN(r) ? 14 : r >= 1 ? 18 : 18 * r;
const h = isNaN(r) ? 14 : r >= 1 ? 18 / r : 18;
return '<span class="sp-box" style="width:' + w + 'px;height:' + h + 'px"></span>';
}
bar.innerHTML = PRESETS.map(function (p) {
return '<button type="button" data-id="' + p.id + '" aria-pressed="' + (p === current) + '">' + boxIcon(p.ratio) + p.name + '</button>';
}).join('');
const cropper = new Cropper(img, {
aspectRatio: current.ratio,
viewMode: 1,
autoCropArea: 0.9,
responsive: true,
crop: function (e) {
document.getElementById('spSel').textContent = 'Selection ' + Math.round(e.detail.width) + ' × ' + Math.round(e.detail.height) + ' px of the original';
},
});
function choose(p) {
current = p;
cropper.setAspectRatio(p.ratio); // NaN removes the lock
bar.querySelectorAll('button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.id === p.id)); });
document.getElementById('spName').textContent = p.name;
document.getElementById('spSize').textContent = p.w ? 'Output ' + p.w + ' × ' + p.h : 'Output at selection size';
}
bar.addEventListener('click', function (e) {
const b = e.target.closest('button');
if (b) choose(PRESETS.filter(function (p) { return p.id === b.dataset.id; })[0]);
});
document.getElementById('spGo').addEventListener('click', function () {
const opts = current.w ? { width: current.w, height: current.h, imageSmoothingQuality: 'high' } : { imageSmoothingQuality: 'high' };
const out = cropper.getCroppedCanvas(opts);
if (!out) return;
const cv = document.getElementById('spCanvas');
cv.width = out.width; cv.height = out.height;
cv.getContext('2d').drawImage(out, 0, 0);
document.getElementById('spResult').hidden = false;
document.getElementById('spResText').textContent = 'Exported ' + out.width + ' × ' + out.height + ' px, ready to post.';
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Cropper.js Aspect-Ratio Presets — HTML, CSS & JavaScript

Every social platform wants a different rectangle. Instagram takes 1:1 or 4:5, YouTube thumbnails are 16:9, Stories and Reels are 9:16 and link previews use the roughly 1.91:1 Open Graph shape. Marketers and editors reframe the same photo several times a week, and a tool that makes them do it with a free-form box and a calculator is a tool they stop using. A preset bar turns that into one click per destination.
Cropper.js supports this directly: aspectRatio in the options locks the crop box to a ratio, and setAspectRatio() changes it at runtime. Passing NaN removes the lock entirely, which is how the "Free" preset is implemented — an easy detail to miss, because the documentation describes NaN as "free ratio" rather than showing a constant. Each preset in the snippet is data — a ratio plus the exact pixel size the platform expects — so adding a destination means adding a line to an array, not writing new logic.
There is an important distinction between the crop and the export. The crop box selects a region of the original, whatever its pixel size; the export decides the output resolution. Calling getCroppedCanvas({ width, height }) resamples the selection to the preset's dimensions, so a 2400 × 1600 source cropped for a 1080 × 1350 portrait post comes out at exactly 1080 × 1350 with high-quality smoothing. The live line under the preview shows the selection size in source pixels through the crop event, which is what warns you when a selection is so small that upscaling will look soft.
The preset buttons draw a proportional outline icon for each ratio, computed from the ratio itself, and use aria-pressed so the active preset is exposed to assistive technology as well as shown visually. The result panel demonstrates the final canvas at its real dimensions, scaled down for display. In a production tool the canvas would be sent on with toBlob() and a fetch upload rather than displayed.
Step by step
How to Use
- 1Choose a presetClick Instagram portrait, YouTube thumbnail, Story or Link preview. The crop box snaps to that ratio.
- 2Adjust the cropDrag the box or its handles to frame the subject; the ratio stays locked.
- 3Read the sizesThe footer shows the output size for the preset and the selection size in original pixels.
- 4ExportPress Export crop to render the selection at the platform's exact pixel dimensions.
- 5Go freeChoose Free to remove the ratio lock and draw any shape you like.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Call setAspectRatio(NaN). NaN means a free ratio, so the crop box can take any shape.
The crop selects a region of the original. getCroppedCanvas with width and height then scales that region to the size you want to publish.
It tells you how many original pixels you have. A very small selection exported at 1080 pixels will be upscaled and look soft.
Add an object to the PRESETS array with its ratio and output width and height. The buttons and icons are generated from it.
Call canvas.toBlob() with a type and quality, then send the blob in a FormData body with fetch.
Yes. Cropper.js supports touch dragging and pinch to zoom on the crop area.
Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Cropper.js, so in a framework project install it with npm install cropperjs instead of the CDN tag, create it in useEffect / onMounted / ngAfterViewInit once the image has loaded, and release it with destroy() when the component unmounts.