Repository navigation
Space between tiles on fractional zoom levels #3575
Description
Activity
No idea. :( Although I only saw the problem in Safari, — Chrome/Opera worked fine.
Does anyone have an idea how this can be fixed?
I have an idea, but it's linked to the words "radical" and "experimental":
https://github.com/IvanSanchez/Leaflet.gl
One of the things I discovered is that textures have to be clamped. i.e. if this line is removed : https://github.com/IvanSanchez/Leaflet.gl/blob/master/src/core/GlUtil.js#L74 slivers ("spaces between tiles") appear (as the texture wraps around the triangle and half a pixel of the other side of the tile is shown). So, my educated guess is that Webkit is antialiasing half a pixel on the edge of each image.
As of now, the tilelayer triangles form a complete mesh that is rendered without slivers... but the words "experimental" will be all over that for the time being.
@mourner It has a stronger effect in Safari but it is also visible in Chrome.

@IvanSanchez Wow nice work, but I don't think that I can use that (yet).
Reacted by Igor Krupenja@Eschon yeah, I probably didn't notice this because on Retina screen, it's even less visible.
I doubt this will be possible for 1.0 without a lot of hacking. Pushing to the bottom of the backlog.
We experience the same problem on latest Chrome too. I wish that you find a possible fix as OpenStreet maps do not show well with these lines. Thanks.
Especially on darker maps or images (in my case) the issue is quite noticeable. I think that antialiasing in the browser causes the spaces between tiles due to the fractional transform during zooming, as mentioned by Ivan.
Until a better solution is available, I will use this workaround (or hack) to make the tiles 1 pixel larger, with the side effect that they overlap by 1 px. Besides that the tiles will be slightly enlarged and scaled (by 1px).
/* * Workaround for 1px lines appearing in some browsers due to fractional transforms * and resulting anti-aliasing. * https://github.com/Leaflet/Leaflet/issues/3575 */ (function(){ var originalInitTile = L.GridLayer.prototype._initTile L.GridLayer.include({ _initTile: function (tile) { originalInitTile.call(this, tile); var tileSize = this.getTileSize(); tile.style.width = tileSize.x + 1 + 'px'; tile.style.height = tileSize.y + 1 + 'px'; } }); })()
Reacted by Eric Socolofsky, Tommy Unger, wandergis, Jerred Shepherd, Shawn, James Collins, Adam Curtis, Cmen, Joseph (Jos) Smith, daniella and 22 moreReacted by n3tman, wjl61901, Sergey Konstantinov and Vladan KudláčReacted by Shawn, Brienna Herold and NienkeVogelzang98@cmulders Thanks for the workaround!
Since we mainly have maps of ski resorts that are mostly white and blue. We ignored the spaces for now, with your workaround it looks a lot better.Made a jsbin with cartodb dark tiles, the lines are visible even on retina screen:
http://jsbin.com/ratoli/5/edit?html,js,outputSafari / Chrome buggy, FF OK for me.
215 remaining items
Load more actionsThis one works with leaflet@1.7.1 But not with the latest leaflet@1.9.4
#3575 (comment) CSS worked for me with Leaflet 1.9.4. My tiles are 256px so I changed the example to match and the gridlines disappeared.
Unfortunately, there was a small scaling issue where tiles touched, causing an occasional jagged edge. I tried setting the
widthandheightto "256.1px" (instead of "257px") and that worked perfectly. The IDE warns that different browsers may handle this differently but I saw great results on both Chrome and Firefox.- marked Resolution and White grid lines #9399 as a duplicate of this issue
on Feb 28, 2025 It looks like chrome has finally implemented some fixes, let us hope ... https://issues.chromium.org/issues/40084005
Reacted by indigoxela, Holger Jeromin and VictorDev200- linked a pull request that will close this issueApply plus-lighter workaround only at specific zoom levels to fix white grid #9560
on Feb 28, 2025 Here is another solution which was found: openstreetmap/iD#10747
I'm not sure if it's the best way, but I tested it by adding this SVG in the HTML
<svg> <defs> <filter id="alpha-5"> <feComponentTransfer> <feFuncR type="identity"></feFuncR> <feFuncG type="identity"></feFuncG> <feFuncB type="identity"></feFuncB> <feFuncA type="linear" slope="5"></feFuncA> </feComponentTransfer> </filter> </defs> </svg>and this CSS
.leaflet-container img.leaflet-tile { filter: url(#alpha-5); }
And I'm not sure why but in my testing it doesn't work as well as for openstreetmap-iD, only fixing the grid at 90% and 110%, but not completely at other zoom levels.
Reacted by Samuel ErbThe mix-blend-mode #8891 didn't work for me, it also break the workaround on my Mac.
I am using Chrome v136.0.7103.114


Google "fixed" the issue on their end but only fixed one part of it, splitting the rest into a new issue. If you're encountering this, add a +1 to the new issue:
Reacted by Michael V. Coppola, VictorDev200, Santiago García Cabral, Matej Murín, terenceng2010 and Boris VerkhovskiyFound yet another exotic issue with the
mix-blend-mode: plus-lighter;workaround.With the workaround enabled, i.e. using standard Leaflet v1.9.4, print-to-pdf output in Firefox blows up the file size, and the map appears blurry on the pdf.
Tested with Firefox 144 on Ubuntu.
This is fixed by disabling the
mix-blend-mode: plus-lighter;CSS rule.Steps
- Open leafletjs.com in Firefox
- Select
Print...=>Save to PDF
Comparison
Default output printing leafletjs.com to pdf
leafletjs.com_Firefox_print_base.pdf (2.4 MB)
Output with mix-blend-mode workaround disabled
leafletjs.com_Firefox_print_disable-mix-blend-mode.pdf (706 kB)
- added a commit that references this issue
on May 18, 2026 One more case that
mix-blend-mode: plus-lighter(1.9.4) doesn't cover: a 1-pixel dot where four tiles meet, at an integer zoom on a display with a fractionaldevicePixelRatio(1.75 here, Chrome and Vivaldi).
It looks like sub-pixel tile positioning: with integer CSS positions, every tile lands at a fractional device pixel (e.g. 955 CSS px → 1671.25 device px), so all edges are anti-aliased.
plus-lightermakes the two coverages along an edge add up to 1, but at a corner the four coverages seem to add up to slightly more than 1:- The dot is brighter than its surroundings and shows on mid tones only. On a flat
#808080layer it's obvious. On black, white or pure red/green/blue it's invisible, because 0 stays 0 and 255 clips. - All the dots disappear and reappear together while panning, as the fractional offset changes.
So hillshading, which is mostly mid-grey, is the worst case.
Workaround, deployed on freemap.sk: set a CSS
translateof under one device pixel on the map pane, so that a resting tile's (scale(1)container)getBoundingClientRect()lands on whole device pixels. All tiles then sit on the device grid, and there's no partial coverage left to add up. It does nothing at an integerdevicePixelRatio, or at fractional zoom, where tiles are scaled. Leaflet's coordinate math ignores the nudge, which is under a CSS pixel.function snapToDevicePixels(map) { const pane = map.getPane('mapPane'); const snap = () => { const dpr = window.devicePixelRatio || 1; const tile = [...pane.querySelectorAll('.leaflet-tile-container')] .find((c) => c.style.transform.endsWith('scale(1)')) ?.querySelector('.leaflet-tile'); if (Number.isInteger(dpr) || !tile) { pane.style.translate = ''; return; } const [dx = 0, dy = 0] = pane.style.translate.split(' ').map((v) => parseFloat(v) || 0); const { left, top } = tile.getBoundingClientRect(); const off = (v) => (Math.round(v * dpr) - v * dpr) / dpr; pane.style.translate = `${off(left - dx)}px ${off(top - dy)}px`; }; // `layeradd` fires per marker too: coalesce to one layout read per frame. let frame = 0; const snapSoon = () => { frame ||= requestAnimationFrame(() => { frame = 0; snap(); }); }; // Re-snap when the window moves to a screen with another pixel ratio. let dprQuery; const onDprChange = () => { watchDpr(); snap(); }; const watchDpr = () => { dprQuery?.removeEventListener('change', onDprChange); dprQuery = matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`); dprQuery.addEventListener('change', onDprChange); }; watchDpr(); map.on('move zoomend viewreset resize', snap); map.on('layeradd', snapSoon); snapSoon(); // layers added before this was attached }
- The dot is brighter than its surroundings and shows on mid tones only. On a flat
https://issues.chromium.org/issues/401515597 for Chrome has seen some movement. It looks like it's been fixed but that fix was behind a flag (and probably an "experiment") to validate it before turning it on by default and closing the bug.
Reacted by indigoxela
As the screenshot shows there is a small space between the tiles. This happens to me on fractional zoom levels in Safari, Chrome and Opera. It seems like this was fixed in #2377 but later removed in 3ccbe5b in favour of better performance.
I tried reimplementing the fix to see the performance impact myself and it was pretty bad furthermore it didn't even fix the problem for Chrome.
Does anyone have an idea how this can be fixed? Fractional zoom levels would be a great feature for our app but as long as this bug remains we can't really use them.
Edit: I set up a jsFiddle that shows the problem: http://jsfiddle.net/Eschon/pw9jcjus/1/