Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Render leaflet map after flexbox calculates height

I am trying to draw a map in a flex child that takes up remaining space.

I have a couple headers on top of the main content, and one of the banners is sometimes hidden/removed, so I am using flexbox so that the header can go away without affecting the other elements position. I.E this does not work well when I use absolute positions.

My problem is that it seems like leaflet is calculating the maps viewport before flexbox has calculated height for the map div.

Example:

Here is a screenshot of leaflet thinking the maps height is smaller than it should be.

enter image description here

If I check the elements height it is correct. And furthermore if I put a timer in and force leaflet to invalidate the map size, the map re-renders at the correct height.

HTML:

<body>
  <div class="header">
      Header
  </div>

  <div class="banner">
      Banner
  </div>

  <main class="main">
    <div class="nav">
      <strong>Navigation</strong>
    </div>

    <div id="map" class="map-content"></div>   
  </main>


    <script src="script.js"></script>
  </body>

CSS:

html, body {
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
}

.header {
    height: 50px;
    background-color: grey;
    color: white;
}

.banner { 
    height: 50px;
    background-color: lightgrey;
    color: white;
}

.main {
    display: flex;
    flex: 1 1 auto;
    flex-direction: row;
}

.map-content {
   flex: 1 1 auto;
}

.nav {
    background-color: #2A3D4A;
    color: white;
    width: 200px;
}

EDIT:

I have reproduced the problem in a plunk, hooray! However I still don't know exactly what is happening. The problem only occurs when I include ngAnimate in my application. Doesn't matter if I use ngAnimate or not.

Here is the plunk that is broken. On initial page load the map is fine. Click the about link, then go back to the map. Notice that when going back, only about half the map loads. I log the height to the console. When you nav away from the map and come back its always about half the size.

What is angular animate doing to cause the element to be half to size for a split second? Is this a bug in angular?

http://plnkr.co/edit/dyBH1Szo3lIEWajKqXWj?p=preview

like image 280
lostintranslation Avatar asked Apr 01 '16 17:04

lostintranslation


3 Answers

It looks like the leaflet map gets the height when created and then you need to call .invalidateSize() if the height changes. In the docs for leaflet it states Make sure the map container has a defined height, for example by setting it in CSS (see http://leafletjs.com/examples/quick-start.html). Since you are not setting the height explicitly but instead have css rule position: absolute; top:0; bottom: 0; left: 0; right: 0; it is being a bit dodgy.

I believe the problem is that when you include ngAnimate it has an effect on elements with transition properties, and somehow the correct height doesn't get seen by leaflet in time. I think you will need to use a workaround e.g. detecting a change in height and calling .invalidateSize()

Try adding this to your directive:

link: function(scope, element) {
  scope.$watch(function() { return element.parent().height(); }, function (val) {
    console.log('height changed to: ' + val);
    scope.map.invalidateSize();
  });
},
scope: true,

Make your controller set $scope.map = L.map('map',{crs: L.CRS.EPSG4326}).setView([0,0], 4); so the leaflet map object is on the scope, and remove the css rules you have on .map-content

like image 79
Mike Jerred Avatar answered Nov 15 '22 19:11

Mike Jerred


I had a similar problem. What was happening is that as directives were loading, a post link function calculated the clientHeight of a div element. The div fills remaining space with flex. But it always was off by some amount. I tracked it down to angular calling the post link before all template partials had been retrieved from the web server. Another directive was still waiting on its urlTemplate, and once it came in, that directive caused another div to change height.

If I just put a timeout of enough time, it worked around the problem. Also, if I broadcast an event from the late div and waited on that before calculating the height, this also works around the problem.

I could not find an easy way to tell when all templates had been loaded, and all directives had done their thing manipulating the DOM. Directive priorities did absolutely nothing to change the order of things.

Look at sequence of events in the browser and see if it isn't a partial template arriving late that is throwing off the timing enough so that calculations are not exactly performed when you asked them to be performed.

like image 2
Jim Flood Avatar answered Nov 15 '22 18:11

Jim Flood


Edited plunkr : http://plnkr.co/edit/pIg9HKwkl0Bfhk4N3wQX?p=preview

try to "invalide" the map like this :

app.directive('leaflet', ['$document','$timeout', function($document,$timeout) {

  function controller() {
    console.log('in leaflet directive controller');

    var element = $document.find('#map');
    console.log('main container height: ' + element[0].offsetHeight);

    var map = L.map('map',{crs: L.CRS.EPSG4326}).setView([0,0], 4);
    L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

    // the timeout wait the view to render
    $timeout(function () {
       // this rebuild the map
       map.invalidateSize();
    }, 0);
  }

  return {
    template: '<div id="map" class="map-content"></div>',
    controller: controller,
    replace: true
  }

}]);
like image 1
AlainIb Avatar answered Nov 15 '22 19:11

AlainIb