How to fix canvas size in Mapbox GL?
Asked Answered
S

9

23

I'm using Mapbox GL to show a map and crop a fixed size image from the center of it. It works great for a particular resolution I designed it (1920x1080) but when I started to make the page responsive where the map styles width and height changes, the canvas size also started to change!

So, when I crop the image the size should be always different because 300px on a 900px canvas it's not the same map area as 300px on a 2000px canvas. The canvas size even changes drastically if I change the device type from Desktop to Mobile in Chrome.

Is there any way to make the canvas DOM size fixed while scaling the entire map with CSS attributes as it's done on a normal canvas? I tried doing trackResize: false and the canvas DOM size stays fixed but the map is not scaled to fit the container neither.

Spice answered 10/2, 2017 at 2:12 Comment(1)
Thanks for asking this dude, saving me hair losses hahahaBenjy
P
21
map.on('idle',function(){
map.resize()
})

Try this. it works fine for me.(for mapbox users only)

Prostyle answered 12/3, 2021 at 12:6 Comment(1)
map.resize() works for me, thank youFining
J
18

for version: "mapbox-gl": "^1.2.1", set height for canvas map container class:

.mapboxgl-canvas-container {

    height: 100vh;

}

then on map load event:

onMapLoaded(event) {

    event.map.resize();

}
Jamal answered 21/8, 2019 at 9:14 Comment(1)
although it does resize the map, it happens right in the eyes of the userMeekins
M
2

I this the best approach would be to invalidate the map's size before it's loaded, forcing it to resize itself.

In Angular/Ionic

import { AfterViewInit, Component, OnInit } from '@angular/core'
import * as  mapboxgl from 'mapbox-gl'

@Component({
  selector: 'app-map',
  templateUrl: 'map.page.html',
  styleUrls: ['map.page.scss']
})
export class MapPage implements AfterViewInit, OnInit {
  private map: mapboxgl.Map  

  constructor() {
    mapboxgl.accessToken = "<YOUR_TOKEN>"
  }

  ngOnInit() {
    setTimeout(() => this.buildMap(), 0);
  }

  ngAfterViewInit() {
    setTimeout(() => this.map.invalidateSize(), 0);
  }

  buildMap() {
    let conf = {
      container: 'map',
      style: '<YOUR_CUSTOM_STYLE>'
      zoom: 13,
      center: [lng, lat]
    }
    this.map = new mapboxgl.Map(conf)  
    // Add map controls
    this.map.addControl(new mapboxgl.NavigationControl())
  }
}
Meekins answered 29/2, 2020 at 13:51 Comment(3)
found that this option works but the user can still notice a sudden increase in the map's size (especially on slower devices. the easiest and most full-proof way way is to add css to the base index.html file in the head section as indicated in the mapbox-gl docsMeekins
Can you link to the section in the docs that you mention? Also, why do you use setTimeout? Why not run immediately? Is this an Angular pattern that I'm not familiar with?Downcomer
Dude, I don't understand why but this is working, even without using map.invalidateSize (Property invalidateSize doesn't exist on type "Map"). I think the key is the setTimeout, but I don't understand why.Benjy
F
0

If I understand you correctly, you want the map to always show the geographical area, no matter how big or small the map container is? That's opposite to the standard behaviour of most web maps, which keep the scale the same, and expand or reduce the coverage area.

If that's what you want, you can probably achieve it by calling map.fitBounds() whenever your window resizes.

Flowered answered 24/2, 2017 at 1:48 Comment(1)
They are asking how to set the height and width of the canvas relative to the DOM, not within the map.Thalamus
E
0

According to @davejoem answer, I was able to display the full map height without stretching by putting a setTimeout outside the buildMap() function. I didn't had to use invalidateSize(), as it throws this error:

error TS2339: Property 'invalidateSize' does not exist on type 'Map'.

Tho, I had to put the setTimeout of buildMap() in the ngAfterViewInit() function and put 2000 on second parameter of setTimeout, because some times with hard refreshing, the map was not full height.

Hope this could help to fully resolve the problem.

Epic answered 21/5, 2020 at 15:1 Comment(0)
B
0

Im working with angular 9 and this worked for me this way: ngAfterViewInit() { setTimeout(() => this.buildMap(), 2000); }

Bruin answered 28/5, 2020 at 14:24 Comment(0)
S
0

I don't know if this will help someone but for some reason the device pixel ratio of the window in a desktop is 1 and in a mobile device is different.

If you're trying to make your map responsive and it f's up when you change to mobile is because your device pixel ratio is diferent from one platform to the other.

try getting your device pixel ratio using:

window.devicePixelRatio

And work with it.

In my case I was working with fragment shaders trying to paint circles using a combination of the position on the map and the zoom. But since fragment shaders works using the canvas.width (and not canvas.clientWidth) when I switched to mobile the amount of pixels representing 100meters changed, I used the device pixel ratio to then convert this bigger amount of pixels into meters using the device pixel ratio...

In case anyone is having trouble with MapboxGL-JS fragment shaders and pixels to meters conversions:

uniform float u_dpr; // the window.devicePixelRatio

//v_rad is radius in meters
//40075017.0 circunference of earth
//cos(-0.296) is based on my latitude
//etc.. 
//multiply the final value by the pixel ratio to get responsiveness working 

float maxDist = (v_rad / (40075017.0*cos(-0.296)/pow(2.0, (u_zoom+9.0)))*u_dpr);
Scum answered 25/5, 2021 at 21:41 Comment(0)
D
0

I am using angular 10.2.x and the below code worked for me

ngAfterViewInit() {        
    setTimeout(() => this.buildMap(), 200);
}
Debit answered 19/9, 2023 at 13:53 Comment(0)
D
-2

The solution

<div
    ref={el => this.mapContainer = el}
    style={{
            position: 'absolute',
            top: 0,
            bottom: 0,
            width: '100%',
            height: '100vh',
     }}
 />
Detainer answered 19/2, 2020 at 19:33 Comment(3)
is this an angular specific solution?Meekins
no, only for React js, but you can to Implementation. The magic is width and the heightDetainer
This doesn't answer the question, which is about the map area shown. Also I'm not sure why you're mixing units of % and vh, which has nothing to do with the solutionDowncomer

© 2022 - 2024 — McMap. All rights reserved.