Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disabling interpolation, when zooming in on an <img>

Tags:

html

css

image

When you have an img element on a HTML5 page, interpolation is applied to the img,
when the user zooms in on the page.
But if you for example have some pixelart on your page and don't want the interpolation,
simply because it's ruining the sharp pixels, what do you do?
My question is simple: How do you disable interpolation in pure HTML5/CSS?

like image 561
Christian L.W. Avatar asked Oct 16 '25 23:10

Christian L.W.


1 Answers

2025 update

Current browsers really settled on pixelated value of the image-rendering property after 2015, so that's a good start.

Yet modern technologies brought new challenges: first, modern displays often have DPI that does not map to CSS pixels in the "default" zoom, or, second, full page zoom can introduce such uneven mapping alone, what produces distracting irregularities in sharp patterns due to visual rounding:

With devicePixelRatio 1.2 grid fine pattern is either blurry or (default) or uneven (when pixelated)

The only way to prevent rounding errors technically is to make sure that the image dimensions match multiples of physical display resolution. This currently is not possible to do in CSS alone (and likely never will (*)), but with the help of minimal scripting we can get the devicePixelRatio JavaScript is allowed to read and provide it for the CSS via custom property. Then use we can use for it for adjusting dimensions. It is not nice, and brings other challenges, but for simplest use-cases in newest browsers is viable:

function setDevicePixelRatio() {
  document.documentElement.style
  .setProperty(
    '--devicePixelRatio',
    devicePixelRatio
  );
  dppx && (dppx.value = devicePixelRatio);
};

setDevicePixelRatio();

window.addEventListener(
  'resize',
  setDevicePixelRatio
);

[...document.images].forEach(img=>img.src=`
data:image/png;base64,iVBORw0KGgoAAAANSUhEU
gAAADwAAAAkAQMAAADvt8vHAAAABlBMVEX///8AAABV
wtN+AAAAI0lEQVR42mN8vHrVaiCay8QABXAGo0xoWCg
QhWCRGtWFJAUA8tQv3Waed9QAAAAASUVORK5CYII=`);
.pixelated {
  image-rendering: pixelated;
}

.rounded-up {
  zoom: calc(
    round(up, var(--devicePixelRatio))
    / var(--devicePixelRatio)
  );
}

.rounded-down {
  zoom: calc(
    round(down, var(--devicePixelRatio))
    / var(--devicePixelRatio)
  );
}

th {
 font-weight: normal;
 text-align: left;
}
<table border>
<tr>
 <td><img class="normal">
 <th>Normal, possibly blurry.
<tr>
 <td><img class="pixelated">
 <th>Pixelated, possibly distorted.
<tr>
 <td><img class="pixelated rounded-up">
 <th>Pixelated + rounded up to nearest whole
 physical pixels, should be larger or same
 size, but OK and crisp.
<tr>
 <td><img class="pixelated rounded-down">
 <th>pixelated + rounded down to nearest whole
 physical pixels (or same as default, if DPPX
 is less than 1). Should be smaller or same, but crisp.
<tr>
 <td><output id="dppx"></output>
 <th>Your current <code>devicePixelRatio</code>,
 try to zoom in and out.
</table>

In this snippet, the "rounded" variants should ensure that pictures will be crisp and without distortions. Sample image is a grid of 3×3 pixels black and white chequerboard. This is how it looks in my browser in 130% page zoom (image is upscaled 4× using nearest-neighbour, but you'll almost certainly get slight "blur" on edges when viewed here in page):

  • computed devicePixelRatio is around 1.42857,
  • "Normal" size is blurry,
  • "Pixelated" variant is the same size as "normal" and displays alternating squares and rectangles in all rows and columns,
  • the "downscaled" variant shows in "true" 1:1 mapping and its squares are sharp 3×3,
  • the "upscaled" variant has sharp 6×6 squares.

(*) In the future, proposed image-resolution could save us from such JS "hacks", but until then, this is probably best what we have.


Notes/Caveats: zoom property is especially convenient here, since – unlike transform: scale() function and scale property – it adjust element in place. But keep in mind it behaves differently from scale

  1. zoom: 0 does nothing, what is a good thing for our purpose, and
  2. it is quite recent addition to our toolset and some browser still struggle implementing it. Historically it was more reliable to reach out for transform and do the chores adjusting layout accordingly. At the time of writing, Gecko-based browsers got rid of last bugs making zoom flawless for this purpose.

Test 2025-04

Here is rather cumbersome test suite comparing pixelation with zoom and scale; results were quite good as of 2025-04-20, with exception for Firefox 138 (Beta), yet Nightly 139 was correct.

function setDevicePixelRatio () {
 document.documentElement.style
 .setProperty(
  '--devicePixelRatio',
  devicePixelRatio
 );
 dppx.value = devicePixelRatio;
};
setDevicePixelRatio();
window.addEventListener(
 'resize',
 setDevicePixelRatio
);
const pic = document.images[0];
document.querySelectorAll('p:empty')
.forEach(
 p => p.append( pic.cloneNode() )
);
document.write(navigator.userAgent)
html {
 color-scheme: dark light;
 /* Never do this in user-facing webpages, please */
 font-size: calc(
  16px * (1 / var(--devicePixelRatio))
 );
 font-family: Courier new, monospace;
}
div {
 display: flex;
 flex-wrap: wrap;
}
p {
 border: 1px solid grey;
 padding: .4ch;
 margin: .25ch;
 position: relative;
 /* Will be used for DPPX cancellation: */
 --factor: 1;
}
p::after {
 content: ' ' attr(class);
}
img {
 border: 3px solid mark;
}
.pixelated img {
 image-rendering: pixelated;
}
.one-to-one {
  --factor: calc( 1 / var(--devicePixelRatio) );
}
.up {
 --factor: calc( round(up, var(--devicePixelRatio)) / var(--devicePixelRatio) );
}
.down {
 --factor: calc( round(down, var(--devicePixelRatio)) / var(--devicePixelRatio) );
}
.scaled img {
 transform: scale(var(--factor));
 transform-origin: top left;
 position: absolute;
 left: 0px;
}
.zoomed img {
 zoom: var(--factor);
}
/* Blank placeholder for positioned img: */
.scaled::before {
 content: '';
 display: inline-block;
 /* Same dimensions and border as image: */
 width: 60px;
 height: 36px;
 border: 3px solid transparent;
 zoom: var(--factor);
 background-color: transparent;
}
devicePixelRatio: <output id="dppx"></output>

<div>
 <p class="default"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAkAQMAAADvt8vHAAAABlBMVEX///8AAABVwtN+AAAAI0lEQVR42mN8vHrVaiCay8QABXAGo0xoWCgQhWCRGtWFJAUA8tQv3Waed9QAAAAASUVORK5CYII="></p>
 <p class="pixelated"></p>
</div>
<div>
 <p class="scaled one-to-one"></p>
 <p class="scaled one-to-one pixelated"></p>
</div>
<div>
 <p class="zoomed one-to-one"></p>
 <p class="zoomed one-to-one pixelated"></p>
</div>
<div>
 <p class="up scaled"></p>
 <p class="up scaled pixelated"></p>
</div>
<div>
 <p class="up zoomed"></p>
 <p class="up zoomed pixelated"></p>
</div>
<div>
 <p class="down scaled"></p>
 <p class="down scaled pixelated"></p>
</div>
<div>
 <p class="down zoomed"></p>
 <p class="down zoomed pixelated"></p>
</div>

<hr>

Results - 50% zoom on 1:1 DPI monitor

  • Edge 135: presumably OK, both "zoom" and "scale" are identical
  • Nightly 139: OK, same as Edge
  • Firefox 138 (Aurora / Dev edition): Scale behaves strange]

Results - 150% zoom on 1:1 DPI monitor

  • Edge 135: presumably OK
  • Nightly 139: OK, same as Edge
  • Firefox 138 (Aurora / Dev edition): Really strange discrepancies between "zoom" and "scale"

Side note: This test brutally abuses the devicePixelRatio for effectively negating the "virtual zoom" and keeping the text-size visually stable in all zoom levels. This is something what might seem tempting to do for some freaky "design consistency", but in fact doing it in any normal context would be blatant violation against accessibility, so don't do that outside these marginal tests.


Original answer (2015)

img {
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: crisp-edges;
    -ms-interpolation-mode: nearest-neighbor;
}

This will affect rendering when you set img dimensions (in CSS or attribute) but is not guaranteed to affect rendering when user zooms page.

MDN: image-rendering CSS property

like image 148
myf Avatar answered Oct 19 '25 13:10

myf



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!