Leaflet: Space between tiles on fractional zoom levels in Webkit browsers

Created on 30 Jun 2015  ·  96Comments  ·  Source: Leaflet/Leaflet

screen shot 2015-06-30 at 14 39 12

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/

bug in progress

Most helpful comment

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';
        }
    });
})()

All 96 comments

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.
screen shot 2015-06-30 at 15 42 07

@IvanSanchez Wow nice work, but I don't think that I can use that (yet).

@Eschon yeah, I probably didn't notice this because on Retina screen, it's even less visible.

This seems to affect Firefox now too in some cases.
I couldn't reproduce it in jsFiddle but if you look at that site
I get the same spaces in Firefox 39 and 41.0a2

Edit: This seems to be a problem with iframes. If I open the map directly it doesn't reproduce.

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';
        }
    });
})()

@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,output

Safari / Chrome buggy, FF OK for me.

We should make a Leaflet-less Chrome bug report for this, so it's fixed in future Chrome versions.

@hyperknot I think there is already a Chrome bug report, but I can't find it right now.

I am using leafletjs 1.0 beta and I am seeing this bug as well sometimes on ( latest: chrome , safari, firefox ) on osx El Capitan. when I edit an images's transform css property by 1px, the edited tile aligns with the tile next to it.

// ex.
 transform: translate3d(1556px, -81px, 0px);
 // to
 transform: translate3d(1555px, -80px, 0px);

Sometimes this bug does not appear though ( that 1px of difference ) !! not sure why :( but I am currently guessing it has to do with the styling of the map container.

Edit: Say the tiles align correctly on render and does not show any 1px of difference, a gap will visible on map move event. The behavior is like a flash of a 1px border around the tile.

I found a css workaround that gets rid of the problem for me on Safari 9.1 on OS X El Capitan:

.leaflet-tile-container img {
    box-shadow: 0 0 1px rgba(0, 0, 0, 0.05);
}

I can't take much credit however, I took it from the following Stackoverflow answer which also hints at _why_ this helps: http://stackoverflow.com/a/17822836/138103

Using box-shadow solves the problem more directly: it extends the repaint dimensions of the box, forcing WebKit to repaint the extra pixels. The known performance implications of box-shadow in mobile browsers are directly related to the blur radius used, so a one pixel shadow should have little-to-no effect.

Maybe this can be used as a proper fix, though it would probably be good if others tried this first. It works for me however and doesn't seem to lead to any problems in other browsers.

@href Maybe you could make a pull request with that fix? That'd make it easier to test.

@href @IvanSanchez Are you sure that this won't affect rendering performance? I think we should profile how does adding a box-shadow with an alpha opacity affect mobile devices for example, it might have a visible effect on performance, or even possibly disable hardware acceleration.

@IvanSanchez done.

Are you sure that this won't affect rendering performance? I think we should profile how does adding a box-shadow with an alpha opacity affect mobile devices for example, it might have a visible effect on performance, or even possibly disabling hardware acceleration for panning.

I'm not at all sure what the effect of this is. From the Stackoverflow answer this doesn't seem to have an effect, but that's just trusting someone else's opinion on a tangentially related issue. It would surely be a great idea if someone more knowledgable could look into it and profile my workaround.

I just got lucky when I googled around and I wanted others to know.

@hyperknot @href I'll happily perform some benchmarks in a couple of weeks against my set of test browsers.

@IvanSanchez @href Also, it might be good to limit this hack to affected browsers only, not just apply it globally.

@IvanSanchez is there a recommended workflow for profiling Leaflet rendering performance?

Also, it might be good to limit this hack to affected browsers only, not just apply it globally.

I personally only see it in Safari, so it would make sense to limit it for safari. There were mentions of people seeing this in Chrome however. Does anyone still see this bug in other webkit browsers?

It's quite trivial to add a CSS class to the map only for selected browsers, in the L.Map constructor stuff.

@href The issue is present in my Chromium 49

@hyperknot I'm not aware of the best way to profile these kind of things. Let's talk about that this weekend ;-)

@href Also, this fix kinda messes up tiles in my Chromium 49... it blurs the whole image instead of just the edge. Original, with .leaflet-container { background: black }:

fractional-space

With img.leaflet-tile.leaflet-tile-loaded { box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); }:

fractional-space1

:crying_cat_face:

That's weird. The issue is not present for me in Chromium 49 (OS X El Capitan), nor does the box-shadow seem to have any influence either way.

Made an up to date playground: http://playground-leaflet.rhcloud.com/yid/1/edit?html,output

I will be submitting Leaflet-less bugs to Webkit and Blink teams.

OK, successfully made a minimal Leaflet-less example reproducing the issue:
http://playground-leaflet.rhcloud.com/say/1/edit?html,output

Some update from Chromium bug report, from [email protected]

Hey just want to give you guys a quick update. I'm actively working on this issue.

The problem was due to we raster things in an element's local space. If the element has a fractional translation, then the rasterized texture would be pasted onto the screen with the fractional translation using linear resampling, resulting in the blur.

The solution is to raster things in a space that is pixel-aligned to our physical screen. i.e. applying the fractional translation to the vector graphics commands instead of rastered texture.

The fix will be coming in two parts:

  1. The first part that allows our raster system to raster with any general matrix. This part is almost done. I have a WIP but it still has a bug that causes performance regression. I expect to finish it within a few days.
    https://codereview.chromium.org/2075873002/
  2. The second part that allows our tiling management to be able to manage set of textures that comes with different raster translation. I was originally going for general matrix but turns out the tile coverage computation becomes very difficult. I will instead do a simpler version that only supports translation and scale. I estimate this will need another week of work.

I will try to land this by M53 branch but time is tight. Pretty sure this can be fixed by M54.

https://bugs.chromium.org/p/chromium/issues/detail?id=600120

To the bug reporter:

The test case you uploaded triggered an edge case in Chromium that we handled pixel snapping in a different way. If you add some contents to the tiles you will no longer see seams in the background. We can easily fix the edge case, however, there can still be seams in the tile contents.

This is a hard problem, and the only known perfect solution is to use FSAA, which no browsers do due to the CPU/memory costs. Every vendors develop their own heuristics to make some contents look nicer, at the costs of some contents look incorrect.

For example: http://jsbin.com/wayapiz/

Chrome draws with the right geometry, but edges bleed like a stuck pig.
Firefox draws the first box as 49x49, the second box as 49x50.
Edge draws the first box as 50x50, the second box as 50x49. Both with aliasing.

Also if you add rotation to the container, all implementation bleed the edges.

My recommended workaround is to add some overlap between tiles. You know, just like making a poster with A4 papers. The idea is to make sure each tile overdraw at least one pixel after all the scaling, so each physical pixel is guaranteed to be covered by at least one opaque pixel. The width of the overlap depends on the smallest scale you want to support. For example, if you want to support down to 1/4 size, then add 4 pixels of overlap.

Below are the detailed analysis for other developers who wants to tackle this.

The repro uploaded by the reporter wasn't a very good repro because it triggers an optimized code path in cc. When we detected a layer completely solid color, we draw it as a SolidColorLayerImpl, which doesn't have the concept of contents scale. Here is a simplified repro: http://jsbin.com/hodoxew/

The black box will draw as a SolidColorLayerImpl of size (100, 100), which will generate a single solid color quad of size (100, 100), which will be projected to the screen by draw transform to become (99.5, 99.5). With anti-alias, the rightmost column and bottom row of pixels will be drawn in semi-transparent.

On the other hand, if we add something to the layer to trick cc into thinking the layer being non-solid color, now the problem comes in two cases:

  1. The last tile being solid color, for example: http://jsbin.com/zexawa/

The contents size will be rounded up, so although the scaled layer size is only (600, 300)*0.995 = (597, 298.5), it will be over-drawn as (597, 299).

  1. The list tile being non-solid color, for example: http://jsbin.com/mewaguf/

The contents size will be rounded up. Again the actual scaled contents only covers (600, 300)*0.995 = (597, 298.5), the generated quads will cover (597,299). The last row of pixels are supposed to be semi-transparent due to anti-alias, but we actually fill it with layer background color, which is the background color of the element that created the layer.

There will be no seam in the background, however, background can bleed on the edges when the contents are supposed to cover it. For example: http://jsbin.com/vigufo/

An other test case, using non-solid color tiles:
http://playground-leaflet.rhcloud.com/giqo/edit?output

@hyperknot fantastic update, but those two quotes seem to contradict each other (in one, a person says he works on a fix, and the next one says no good fix possible). So do we wait for a fix or just accept that it will be this way forever?

@mourner sorry I didn't include the full context, and you are right of course. So original bug report was:
https://bugs.chromium.org/p/chromium/issues/detail?id=600120

At one point it got merged into an other issue:
https://bugs.chromium.org/p/chromium/issues/detail?id=521364

In that other issue, we got the original long reply with a solution. I asked if he could check if the Leaflet case is indeed fixed by his work and he replied:

I'm sorry the leaflet issue is in fact a different problem. I will re-open issue 600120 and share my analysis.

So the Leaflet bug is now reopened and we got the 2nd reply in the Leaflet bug one. BTW, the latest update right now is:

I took a look at mobile Google map to see how they handled it. It turns out they are plagued by this problem too, but they did some UI trick to make it less obvious.

The first thing is they used the viewport tag to disallow browser pinch zoom, and implemented pinch zoom in JS. During pinch zoom, you can still see seams between tiles (very subtle on high-res displays). After the zoom gesture finish, they snap the scale factor to the closest tile set they have. So basically tiles are always displayed in native resolution except during zoom gesture.

Honestly we don't have a solid plan yet. I'm going to discuss with other paint people on Monday.

BTW, it seems that mobile Google Maps (I guess the website one) is using exactly the same technique as us.

@mourner @IvanSanchez some hard-core question with a possible solution using will-change:
https://bugs.chromium.org/p/chromium/issues/detail?id=600120#c15

Is the current layerization important? I see each tile is forced into its own layer (translate3d), and these layers are scaled by some fractional value independently.

As long as we scale each map tile independently (either using layers or via drawImageRect + fractional CTM), for fractional scale values we get edges which are not pixel-aligned. This triggers the ole coincident edge anti-aliasing problem where discrete AA causes background color bleed.

The way I think this could work instead is to rasterize the map with a non-fractional scale (say 1.0) and then downscale atomically:

1) avoid individual tile layerization (use "transform: translate()" instead of "transform: translate3d()")
2) force container (whole map) layerization (use "transform: scale3d()" on the container)
3) force container layer rasterization with a scale == 1.0

With these changes, map tile images should be rasterized with a scale of 1.0 (=> edges are pixel-aligned => no seaming artifacts), then the whole map layer should be down-scaled atomically.

Unfortunately #3 is not trivial, and the only hack I can think of is

  • start with scale(1) and will-change: transform (see https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/Ufwrhx_iZ7U for details)
  • reset the layer scale to the desired value after the first raster pass

e.g. http://jsbin.com/yaqeru/10/embed?output

chrishtr/danakj did I get the layer raster scale semantics right, and is there a better way to lock the layer rasterization scale to a particular value?

Can you help answer it?

I know a fix is in progress (thanks all for pushing on that!), but in the meantime...@cmulders' monkeypatch works best for me. Here's an version that brings in Leaflet as an npm module, uses ES6, and ensures no double-patch, in case anyone is looking for some copypasta goodness.

https://gist.github.com/ericsoco/5712076f69f9068b11d41262b9e93666

import leaflet from 'leaflet';
// ...
patchMapTileGapBug();
// ...
function patchMapTileGapBug () {

    // Workaround for 1px lines appearing in some browsers due to fractional transforms
    // and resulting anti-aliasing. adapted from @cmulders' solution:
    // https://github.com/Leaflet/Leaflet/issues/3575#issuecomment-150544739
    let originalInitTile = leaflet.GridLayer.prototype._initTile;
    if (originalInitTile.isPatched) return;

    leaflet.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';
        }
    });

    leaflet.GridLayer.prototype._initTile.isPatched = true;

}

I'm glad people are looking into this. In case useful to anyone as a less than ideal workaround: if you're willing to sacrifice inertia, setting intertia:false on the map init eliminates the gaps, and possibly zoomAnimation:false

I think I've found an (overcomplicated) solution for this, I've put it up at https://github.com/Leaflet/Leaflet.TileLayer.NoGap - comments are welcome, as I'm not sure of the performance of that thing on old computers/phones.

cc @Eschon @hyperknot

BTW, the bug also happens when using Chrome on a high-DPI (2560x1140) screen, without the need to use leaflet fractional zoom nor browser zooming.

Sorry for the late reply.

I just tested your solution. The Demo worked fine for me so I tried to add the fix to my application. Since I was still using an old 1.0-dev version of Leaflet I updated it to 1.0.0-rc3 from http://leafletjs.com/download.html

The first thing that I noticed is that there seems to be an error here. It shows an error that map undefined so I changed it to this._map.

After that it worked but it is really slow on my work computer. Especially in Chrome but Firefox was also slow.

Surprisingly it didn't have that big of an impact on the phones that I tested.
Firefox on Android seems to be the same as before, Chrome is a bit slower but not too bad.
Safari on iOS also seems to be about the same as before.

Another strange thing that I noticed is that on mobile Chrome and Safari pinch-zooming seems to snap to fixed levels

@Eschon Thanks for testing this!

I always make the mistake of typing map instead of this._map :-(

After that it worked but it is really slow on my work computer. Especially in Chrome but Firefox was also slow.

Could you run some profiler on that computer? It'd be nice to know if the slowdown is related to canvas operations. I noticed that was a bit performance hit on IE9-IE11.

Based on whether this solution makes the map slower on some platforms, it might make sense to make it completely optional and turned off by default. Some feedback here is important!

I haven't really looked at how the OL3 folks solve this issue, or whether they do some performance detection on the browser before enabling canvas compositing. Might be worth to have a look.

Another strange thing that I noticed is that on mobile Chrome and Safari pinch-zooming seems to snap to fixed levels

I bet that's just fiddling with the zoomDelta and zoomSnap options, and not related to this bug.

Could you run some profiler on that computer? It'd be nice to know if the slowdown is related to canvas operations. I noticed that was a bit performance hit on IE9-IE11.

Should I just record a JavaScript CPU Profile with the Chrome dev-tools? I don't have much experience with that performance testing stuff so I don't really know what to make of the data it shows.

Yeah, that's the thing. Then look for wide things on the flame chart.

I just did a profile with the fix and another one without it. I tried to do about the same for both profiles.

The first thing I noticed that the version with the fix had an additional "peak" (I don't know if this is the right word) caused by _onZoomTransitionEnd

In the version without the fix _onZoomTransitionEnd took 1.8ms and I didn't even see it in the chart. In the version with the fix it took 6ms and caused a "peak" in activity.

The other two "peaks" look pretty similar in the two profiles things just seem to happen a little different but the gap between them is bigger in the version with the fix.

I hope that helps you somehow. I also saved the two profiles so if you need them I could upload them somewhere.

As a dirty workaround for the time being, I just add a scale factor of .002 to the original function that sets the translate3d property to each tile which eliminates the gaps almost entirely. ".002" was the lowest scaling factor in my test cases to make the gaps fully disappear. I didn't notice any side effects.

Tested in latest Chrome, Firefox and IE 11. Tested on a 40" 4k Monitor with windows scaling set to 150% with various zoom values within the browsers.

any progress on this bug? @AlexanderUhl does your solution causes any modification of images when placed or is this only affected while moving/zooming?

It seems will-change CSS property was added to resolve this issue, only need to check FF performance warnings, described here: https://github.com/Leaflet/Leaflet/issues/4686

@themre : the scale property applies to all image tiles when added and are permanently. The style attr will look like this:
style="width: 256px; height: 256px; transform: translate3d(545px, 745px, 0px) scale(1.002); opacity: 1;"

But that would cause to have image stretched a bit which is an unwanted effect. I tried 1.0.3 version which has will-change CSS property but it still causes white gap. Will try to look some more.

Ofc it stretches the img, thats the approach ;-) I agree that this workaround is not ideal, but tbh that is usually the nature of a workaround, isn't it? Nonetheless, this approach is working best for me with negligible side effects (for my use case which is a mapping application with tiles from mapbox, here, osm etc.)

well, if we use normal translate, the problem is gone. Don't really know if translate3d CSS property is so much faster than normal translate, perhaps it would be best to add this flag as an option.I think I'm gonna replace translate3d with translate.

view in action:
translatemap

image
Facing same issue, with latest version (1.0.3).

it'll be fixed in 1.1 possibly

On 2017. Mar 8., Wed at 10:13, Gaurav notifications@github.com wrote:

[image: image]
https://cloud.githubusercontent.com/assets/13112509/23697357/7dcc4cf6-040d-11e7-881a-df3a44254015.png
Facing same issue, with latest version (1.0.3).


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Leaflet/Leaflet/issues/3575#issuecomment-284987808,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAeKj_23QbtFCaXeFjGRJcU-jg0284xBks5rjnEvgaJpZM4FO8Jh
.

If the values of n.x and n.y, which are used to translate the .leaflet-pane.leaflet-map-pane div are rounded to a whole number, then the line/gap problem is resolved on Safari. I can see no detrimental side effects (for my use anway).

Find this:
translate3d("+n.x+"px,"+n.y+"px,0)")
and replace with:
translate3d("+Math.round(n.x)+"px,"+Math.round(n.y)+"px,0)")

@LudwigBogner it likely fails anyway once you do some scaling (setting browser zoom, for example, or during zoom animation). See this example, linked above, where the translates in themselves are rounded: http://playground-leaflet.rhcloud.com/vicu/edit?html,output

Just got bit by this one. For me it doesn't seem to exist on Chromium or FireFox. Just some of the fractional zooms on Safari.

Progress?

This problem still exists on Chrome and Firefox (Mac). I'm using a zoomDelta and zoomSnap of 0.25.

Oddly enough, setting .leaflet-tile { will-change: auto !important; } (or "unset") fixes the problem in Firefox (only). Still looking for a workaround for Chrome.

The other proposed fixes for disabling inertia and changing the translate3d code to add rounding did not work either.

@jawinn I think you might have success by setting L_DISABLE_3D to true, IIRC it has about the same effect as changing the will-change, i.e. it will remove hardware acceleration when rendering the map. Be advised that it will probably give you much worse performance for zoom animations, panning etc.

All of this is very sensitive to browser implementation details, so take this with a grain of salt, it was quite a while since I looked into this.

@jawinn
The work around that I use is the one by @cmulders above:

/* 
 * 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';
        }
    });
})()

It's not perfect but as far as I can tell it doesn't impact performance

Thanks guys. @Eschon That worked. No more gaps are appearing in Chrome or Firefox.

For those who are going to use the "1px fix", be prepared that your images will become blurry and a bit broken:

For me, who spent hours rendering and photoshopping the original image, this was unacceptable. So I had to use L_DISABLE_3D = true and bear with flickering animations and jumpy zooms in the end..

More info: I use leaflet-rastercoords plugin and my map settings are the following:

    map = L.map('map', {
        center: [3250, 1700],
        zoom: 5,
        minZoom: 2,
        maxZoom: 6,
        maxBoundsViscosity: 1,
    });

Q: What's the next best alternative to Leaflet which doesn't have such "space between tiles" bug?

I'll Google it myself, but maybe someone already took this path.

@n3tman did you also try the NoGap plugin? Might be worth a shot.

@perliedman thank you for mentioning this one.
Unfortunately, couldn't make it work locally.. Got Cannot read property 'appendChild' of undefined error in _initContainer function.
Tried with previous versions of Leaflet (up to 1.0.2) - same result. The demo shows the same error in Chrome/Firefox.
Maybe I'm missing something obvious, can you please assist?

Same 1px gap here.

Chrome Version 66.0.3359.139 (64-Bit)
Windows 10 (1709)
Leaflet 1.3.1

When I initially opened this issue I found that it is related to fractional zoom levels but this might not be the only cause anymore.

I recently started a project that uses the GoogleMutant plugin and there I've found that this also happens when I just set the zoom level to a whole number and just pan around a little bit.

It seems to be related to #6069 and (kind of) fixed by #6240 though the gaps still show up when zooming and panning.

I also removed the workaround from my other projects and it seems to happen there as well so it's not specific to the GoogleMutant plugin

Inspired with @cmulders 1px workaround... It works, but the blurry effect that appears is not nice on some images. I tried the below instead (I have not tested a lot though...) and it seems to fix the gaps and still keep sharp images with non-fractional zoom levels. The additional 0.5px seems to be enough to cover the gaps (tried Chrome and Firefox on Linux and Chrome on Android, with Leaflet 1.3.4)

(function(){
    var originalInitTile = L.GridLayer.prototype._initTile
    L.GridLayer.include({
        _initTile: function (tile) {
            originalInitTile.call(this, tile);

            var tileSize = this.getTileSize();
            var map = this._map;
            var isFix = function() {
                return !(parseFloat(tile.style.width) % 0 === 0);
            };
            var fixOn = function() {
                if(!isFix()) return;
                tile.style.width = tileSize.x + 0.5 + 'px';
                tile.style.height = tileSize.y + 0.5 + 'px';
            };
            var fixOff = function() {
                if(isFix()) return;
                tile.style.width = tileSize.x + 'px';
                tile.style.height = tileSize.y + 'px';
            };
            var checkFix = function(){
                var zoom = map.getZoom();
                if(zoom % 1 === 0) {
                    fixOff();
                }
                else {
                    fixOn();
                }                
            };
            map.on('zoomstart',fixOn);
            map.on('zoomend',checkFix);
            checkFix();
        }
    });
})()

screen shot 2018-09-28 at 3 56 33 pm
More than 3 years later, this bug still hasn't been resolved in Chrome! I don't see the effect in Firefox or Safari. Thank you @cmulders for your work around!

Same here (Windows 7 Enterprise SP1) . Lines here and there in Firefox, complete line grid in Chrome, but OK in IE11. @cmulders fix works in Firefox and Chrome.

Here are complete results of some more testing:

  • Windows 7 Enterprise SP1

    • IE11: OK

    • Chrome, Firefox: not OK

  • Windows 10

    • IE11, Edge, Firefox: OK

    • Chrome: not OK

  • macOS

    • Safari, Chrome, Firefox: OK

  • Linux:

    • Chrome, Firefox: OK

  • Android:

    • Chrome, Firefox: OK

    • App using WebView: OK

Further testing showed this is obviously very low level problem that has nothing to do with Leaflet.
Testing on Windows 7 Enterprise SP1 was initially on two different PCs, but both with NVIDIA graphics.

When the same test was done on Windows 7 Enterprise SP1 with AMD Radeon graphics, it went OK, no lines between tiles on Chrome or Firefox!

As NoGap has been mentioned before, I just wanted to let you know that I took the original code and modified it to make it work with current leaflet versions (i.e. leaflet ^1.3.3):
https://github.com/Endebert/squadmc/blob/master/src/assets/Leaflet_extensions/NoGapTileLayer.js

It can then be used like a regular TileLayer: https://github.com/Endebert/squadmc/blob/master/src/assets/SquadMap.js#L34

It's using es6 functionalities, so you might have to make some modifications to get it working for you.

As NoGap has been mentioned before, I just wanted to let you know that I took the original code and modified it to make it work with current leaflet versions (i.e. leaflet ^1.3.3):
https://github.com/Endebert/squadmc/blob/master/src/assets/Leaflet_extensions/NoGapTileLayer.js

It can then be used like a regular TileLayer: https://github.com/Endebert/squadmc/blob/master/src/assets/SquadMap.js#L34

It's using es6 functionalities, so you might have to make some modifications to get it working for you.

@Endebert Did you manage to get NoGap working? I got the same error as @n3tman some time ago:
Cannot read property 'appendChild' of undefined error in _initContainer function.

The only change I found in your version is adding try ... catch to the call level.ctx.drawImage(imageSource, offset.x, offset.y, tileSize.x, tileSize.y);
and it didn't help.

@TomazicM Yes, my implementation is being used in my app SquadMC. It might not be that obvious on desktop, but on mobile it's apparent that fractional zoom without snapping is enabled.

In regards to the implementation: The original version uses TileLayer.include(), while I use TileLayer.extend(). I believe that's the important difference. As mentioned, it's a new class that is intended to be used instead of TileLayer.

Well, here is my result from 2 hours long investigation. The problem is caused by pixel decimals inside translate3d.

<div class="leaflet-pane leaflet-map-pane" style="transform: translate3d(-10.7773px, -8.41406px, 0px);"></div>

That is why it works well with L_DISABLE_3D = true, because according to this part of source code it does not use translate3d.

Solution

Just do not allow decimal pixels. It can be achieved by, for example:

```js
var pos = (offset && offset.round()) || new Point(0, 0);
```

  • or by many and many another solutions...

Well, here is my result from 2 hours long investigation. The problem is caused by pixel decimals inside translate3d.

I guess that today is 2019 and using translate3d to force modern browser use GPU is quite obsolete. Good job @mdorda

@mdorda Hi! Sounds interesting, but be advised that rounding has been discussed before in this thread: https://github.com/Leaflet/Leaflet/issues/3575#issuecomment-327237235

Not sure if anything important has changed since then that makes a difference, but I would not get my hopes up until we have done some thorough testing.

A first step to resolve this would be to submit a PR so we can test this.

For those working with Angular 2+ I've solved it this way. It works for my simple use case, don't know about other implications...

const tileLayer = new TileLayer(this.tileLayerUrlTemplate, this.tileLayerOptions);
tileLayer.on('load', () => {
    for (const tile of Array.from(document.getElementsByClassName('leaflet-tile'))) {
        (<HTMLElement>tile).style.height = (tile.clientHeight + 1) + 'px';
        (<HTMLElement>tile).style.width = (tile.clientWidth + 1) + 'px';
    }
});
this.map.addLayer(tileLayer);

I see this pixel gap on fractional zoom in LeafletJS version 1.5.1 on Windows 10 Pro 1903 with Mozilla Firefox 69.0.1 (64-bit).
Pixel gap
I think, that the reason of pixel gap maybe not in translate3d() CSS function but in scale().
image
In version 1.5.1, as I see, translate3d() parameters only integer.


With best regards, IMMSPgisgroup

Did You found any solution for this bug?

I have the same problem in this moment and I did not found a solution.

error

I am ussing:

  • Windows 10 Home 1803
  • Google Chrome Versión 77.0.3865.90 (Build oficial) (64 bits)
  • Leaflet 1.5.1

When I use Firefox, the white lines not appear

no_error

@Arenivar93

Na I've had my eye on this for a few years now. They've tried a number of workarounds but nothing has worked to correct it without major compromises. The root of the problem is upstream in chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=600120

The latest message from the chromium team is it's too hard to do without breaking other things so we may be stuck with it for a while.

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';
        }
    });
})()

we've had a good amount of luck with this solution. it's not perfect, but it's better than the lines in our opinion.

in addition to that, we made sure the background color of the map was more in line with the color of the basemap. for instance, if the ocean was a dark blue, we would use that so that the tiles overlay on that color, minimizing some of the inconsistencies that are noticeable.

again not perfect, but it helps

@colbyfayock May I ask where and how do you implement your solution?

i'm building a react app so it may differ, but i created a file with these contents:

// Fix via https://github.com/Leaflet/Leaflet/issues/3575#issuecomment-150544739
import L from 'leaflet';

(function () {
  if (!L || !L.GridLayer || !L.GridLayer.prototype) return;

  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';
    }
  });
})();

the returns at the top are because it precompiles and is not always available (not important here)

then i'm simply importing it after I import leaflet

import L from 'leaflet';
...
import '../plugins/leaflet-tilelayer-subpixel-fix';

My workaround is to round transform value without applying a change to the source code.
Using with react but can be used by any front end since it is JQuery
Not telling it is the best option tho, just makes it done.
This fixes problem and I havent observed any noticable blurring or defect of some sort. Worth a try.

    this.leftMap.on('move', () => {
      const mapDiv = window.document.getElementsByClassName("leaflet-pane leaflet-map-pane")
      let styleString = mapDiv.getAttribute("style");
      const transformValue = styleString.substring(styleString.indexOf("(")+1,styleString.indexOf("px,"));
      const roundedTransformValue = Math.round(parseFloat(transformValue));
      styleString = styleString.replace(transformValue,roundedTransformValue);
      mapDiv.setAttribute("style",styleString);
    });

@colbyfayock - It doesn't seem to fix it for me. :(

I'm using react leaflet with zoom snap 0.

image

the solution never was perfect for us, but we definitely don't have consistent lines like that in ours. i wasn't sure about the zoomSnap feature so had to look it up, I'm not really sure how 0 would work with that property from how I'm reading the description. have you tried removing that just to see if the lines are still present without it?

https://leafletjs.com/examples/zoom-levels/#fractional-zoom

Edit: kept reading after the example gif

zoomSnap can be set to zero. This means that Leaflet will not snap the zoom level.

I'm guessing you're getting into fractional states that the solution can't handle beyond the full integer default snap 🤷‍♂️but not sure

Leaflet 1.7-dev and still the problem persists. Lines at borders of tiles in Firefox and Chrome in Windows 7 and Windows 10. No problem in IE11 and old (non Crome) Edge, same problem in new, Chrome based Edge.

There are two working solutions, but I don't like any of them. One is to disable 3d by <script>L_DISABLE_3D = true;</script>, but this means loosing animated operations. Other is to extend tile HTML elements by 1 pixel, but this means you get slightly blurred tiles.

I found a very primitive solution, but by very nature of being primitive, it works 100%. Maybe somebody finds it useful.

Solution is to display the same offending tile layer twice on two different map panes, one placed below the other and having offset [-1, -1].

Code looks something like this:

<style>
  .originOffset {
    transform: translate(-1px, -1px);
  }
</style>

<script>
  var pane1 = map.createPane('pane1');
  var pane2 = map.createPane('pane2');

  pane1.style.zIndex = 210;
  pane2.style.zIndex = 220;

  L.DomUtil.addClass(pane1, 'originOffset');

  var layer1 = L.tileLayer( ... , {
    pane: 'pane1',
    ...
  });
  var layer2 = L.tileLayer( ... , {
    pane: 'pane2,
    ...
  });
  var layer = L.layerGroup([layer1, layer2]);
</script>

from @cmulders suggestion I have ad these line and no more space between tiles

map.on('zoomend drag', function() {
$('#map > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-tile-pane > div > div > img').each(function() {
if (String($( this ).css("width")).includes('.5') === false) {
var imgW = String($( this ).css("width")).split( "px" ).join( ".5" )
var imgH = String($( this ).css("height")).split( "px" ).join( ".5" )
$( this ).css("width", imgW);
$( this ).css("height", imgH);
}
});

I'm with @mdorda , when panning and zooming results in whole numbers in leaflet-map-pane's call to translate3d(), instead of fractions of pixels, then (with my blinders on) the problem is gone and everything's beautiful again. I don't claim to understand all the nuances of when fractions need to be kept, but if it provides Leaflet some relief from the browser's problem, then would it be acceptable to have L.Draggable._onMove put in some flooring

    offset.x = Math.floor(offset.x / this._parentScale.x);
    offset.y = Math.floor(offset.y / this._parentScale.y);

or even in L.DomUtils.setTransform

    pos.x = Math.floor(pos.x);
    pos.y = Math.floor(pos.y);

so that no translate is given fractions, but that'd probably be wasteful since tile images don't result in fractional translates (right?).

I wasted two hours, to discover after that my browser settings was zoomed in, not in actual size
Just take zoom back to actual and all worked like charm, this was my case

I wasted two hours, to discover after that my browser settings was zoomed in, not in actual size
Just take zoom back to actual and all worked like charm, this was my case

@tokenflow: This topic is literally about 'Space between tiles on fractional zoom levels in Webkit browsers'. I am really trying not to be cocky here but do you really think that your information about zooming to 100% is a useful contribution to anyone?

I've encountered this bug on the Linux version of both Firefox and Chromium.
The problem (at least for Firefox) seems to be the scale() of the main .leaflet-tile-container, which I was able to find with document.querySelector('.leaflet-tile-container:last-of-type').getAttribute('style').
E. g. you have scale(0.707107), but all img children are width/height 256. So you calculate: 0.707107 * 256px = 181.019392px, which is a decimal number. When you then round the pixels and take that as scale, it seems to work in Firefox/Linux: 181px / 256px = 0.70703125.

I've written a function that helps debug:

function getCurrentScale (doFix = false) {
    const tileContainer = document.querySelector('.leaflet-tile-container:last-of-type');

    if (!tileContainer) {
        return;
    }

    const tileStyle = tileContainer.getAttribute('style');
    const scaleRegEx = /scale\(([0-9\.]+)\)/i;
    const matches = scaleRegEx.exec(tileStyle);

    if (!matches || matches.length !== 2) {
        return;
    }

    const scale = parseFloat(matches[1]);

    const mod = (scale * 256) % 1;

    if (mod) {
        console.log('scale is off by px:', mod);
        if (doFix) {
            const newScale = Math.round(scale * 256) / 256;
            console.log('old scale / new scale', scale, newScale);
            const newStyle = tileStyle.replace(scaleRegEx, `scale(${newScale})`);
            tileContainer.setAttribute('style', newStyle);
        }
    } else {
        console.log('scale seems to be fine:', scale);
    }
}

If you call it with true as the first parameter, it attempts to fix the scale. This, however, only works on Firefox for me.

I've found a fix that's stupid and really shouldn't work.

.leaflet-tile-container img {
    width: 256.5px !important;
    height: 256.5px !important;
}

This should result in massive jarring seams around the tiles but I can't see them at all and it fixes the space between the tiles.

@ChrisLowe-Takor somehow it really does seem to work though at first glance I can't really tell why.
This might be preferable to the JavaScript workaround that I'm using right now but I'll have to look into it some more and do some testing but.

@ChrisLowe-Takor - Wow, it works for me as well! Good find 👍

@ChrisLowe-Takor Also seems to work for me in R, using the Leaflet-package. Thanks a lot!

@ChrisLowe-Takor Somehow this trick really does it for all the stupid modern browsers (for old venerable IE11 it's not needed)! There's just one thing to have in mind: if option detectRetina is used for tile layer and display is retina display, tile size will be 125 x 125 px.

But this solution has the same drawback as all the solutions that are based on tile container expansion: tile gets a bit blurred.

@ChrisLowe-Takor I tried it. If maxZoom is greater than maxNativeZoom, the tiles will not display properly.

Was this page helpful?
0 / 5 - 0 ratings