xkjyeah / vue-google-maps

Google maps component for vue with 2-way data binding
https://xkjyeah.github.io/vue-google-maps/
1.88k stars 475 forks source link

marker slot for custom dom element not working #289

Closed eregnier closed 6 years ago

eregnier commented 6 years ago

I am trying the following code for my custom map where I want to display custom markers (in my case, I would like to use svg tag to render clean icons)

<gmap-map
      :center="center"
      :zoom="15"
      style="width: 500px; height: 300px"
    >
      <gmap-marker
        :key="index"
        v-for="(m, index) in markers"
        :position="m"
      >
          <div class"custom-tag"><!--custom marker using vue google map slot feature, magic there not working--><svg>...</svg></div>
      </gmap-marker>
</gmap-map>

therefore, I noticed that the marker implementation for vue2 google maps seems to take care of this behavior

//components/marker.js
render(h) {
   if (!this.$slots.default || this.$slots.default.length == 0) {
    return '';
  } else if (this.$slots.default.length == 1) { // So that infowindows can have a marker parent
    return this.$slots.default[0];
  } else {
    return h(
      'div',
      this.$slots.default
    );
  }
},

But the render is invisible or dom element is not created (dev tools have strange behavior with google map, so I did not mind how to find rendered markers dom elements).

I am currently using svg icon assets that let me display icons only using svg tags with xlink:href values like 'asset.svg#icon_id'. Transclusion works properly on ngMap similar component. Any fix/help would be appreciated.

eregnier commented 6 years ago

I investigated a bit more in this issue. I found that in the ngMap project custom-marker components / directives exists. They are under the hood managing a google map custom overlay. This feature is not (yet ?) available in this project.

So I will try to make my own and if satisfying enough, I will submit it to this project.

However I am not sure to be mature enough on vue js to make a good job. But I'll try :)

rmNyro commented 6 years ago

Duplicate? https://github.com/xkjyeah/vue-google-maps/issues/288

eregnier commented 6 years ago

Hi there. I did one component that fits my needs : https://github.com/eregnier/vue2-gmap-custom-marker However I don't know how to create the appropriate pull request as I don't know how to bundle js lib and even less vuejs libs. I let you (project owner) decide wether or not my code should be integrated to this project. I put a MIT licence that may let you do whatever you want with the code. I hope you'll enjoy.

rmNyro commented 6 years ago

It would be nice (for the owner) if you take some time to answer my question.

If your problem was the same as mine, maybe my (quick and dirty) solution works for you. In that case it's most definitely the the same issue so one patch fix both. If my solution doesn't work for you, then it's bad news because it's 2 separate issues.

Btw, did you manage to fully analyse what was the bug?

eregnier commented 6 years ago

Unfortunately, I did not investigated enough to determine what could be the root cause of this issue. I already spend many time for this issue, and because I am not skilled enough on both vue js and google map js api, I cannot determine how far are my investigations from a clean solution.

However, I think I dug in the marker code of the vue google map lib enough to have the feeling that this issue cannot be solved with the current implementation, and that it requires the use of the Overlay layer as I did in my plugin linked above.

I have no more time at the moment to dig deeper, nor the skills and my plugin is sufficient for my needs at the moment.

rmNyro commented 6 years ago

Alright, I think I get it what you say is there's no native implementation for svg based marker in the lib. It could be implemented in a specific component handling just that.

You may have simply used a path based solution like https://stackoverflow.com/questions/24413766/how-to-use-svg-markers-in-google-maps-api-v3. You don't need to "patch" the lib using this method and if you want to use a file svg, I guess you need to "translate" it into an object. It worked for me.

eregnier commented 6 years ago

Thank you for your time trying to understand me :) Indeed, I may have translated my svg to a google marker appropriate path. In an other hand, my implementation of custom marker let the user "transclude" whatever html dom content he wants as a "custom marker". this is a wider purpose than only displaying svg.

eregnier commented 6 years ago

I would like to follow your idea rmNyro, but in my case, my svg icons are in a svg asset file embeded in the dom. I don't understand how can I use theses icons as in your link. Did you already do this ?

rmNyro commented 6 years ago

I tried the code in one of my projects where I dynamically generate markers.

My code before:

el.marker = new google.maps.Marker({ ...el.marker })

el.marker.setIcon({
    url: e.category.markerIconPath,
    size: new google.maps.Size(iconSize.x, iconSize.y),
    scaledSize: new google.maps.Size(iconScaledSize.x, iconScaledSize.y),
    origin: new google.maps.Point(0, 0),
    anchor: new google.maps.Point(iconScaledSize.x / 2, iconScaledSize.y),
}

Where el contains info like the position, etc... And you can see I set url which is the image path. After a little bit more processing (calculating the bounds, etc...) I end up adding the marker using:

el.setMap(this.$refs.gmap.$mapObject)

Now I just need to kick pretty much everything. My code after:

el.marker = new google.maps.Marker({ ...el.marker })

el.marker.setIcon({
    path: 'M-20,0a20,20 0 1,0 40,0a20,20 0 1,0 -40,0',
    fillColor: '#FF0000',
    fillOpacity: 0.6,
    anchor: new google.maps.Point(0, 0),
    strokeWeight: 0,
    scale: 1,
})

Of course you can use anchor, origin, etc...if needed. In my case it was designed for the image which was the same size as the original marker.

It doesn't seem to work via the :icon bind. I think it's where you code shines but I'm pretty sure you can make it rather short without transforming the svg into an image (if I understand your code correctly).

BUT So if you want to use external svg file you can apparently simply replace url with the url of the svg. As shown in this pen: https://codepen.io/defvayne23/pen/EVYGRw.

Hope it's helpful to you.

eregnier commented 6 years ago

Hey ! Thank you for the answer. You are showing me intersting stuff :+1:

What I am trying to explain is that I want to display on the map svg icons that are embed in an asset. The asset is the following :

https://www.jaccede.com/img/categories.svg (this content is not open source atm)

and these icons are usable like this in the project :

<svg>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://jaccede.com/img/categories.svg#icon-square_lodging"></use>
</svg>

This is how I have to use the asset in order to display icons properly. however, I spend many time trying to :

My component almost work, but is not integrated to this project and is somehow buggy (help would be appreciated) because I really don't master google map api and event less spacial geometry (bad maths even if the topic is quite simple) And , the solution where I could convert the dom svg element to a png image does not fully satisfy (In case I succeed this) , because this implies extra processing that is unecessary and quite expensive on mobile devices for instance. The best for me seems to follow your idea to use existing marker system properly , but with the understanding of how to do it with a svg asset.

Note: I found this project yesterday where I think I can take advantage of the marker implementation that seems to have a better impementation that mine: https://github.com/scottdejonge/map-icons This file should be the one that interests me: https://github.com/scottdejonge/map-icons/blob/master/dist/js/map-icons.js It almost does all the work, but I noticed that It is not well optimized when I tried to use it. this issue explains it: https://github.com/scottdejonge/map-icons/issues/70 No answer at the moment.

The performance concern is necessary in my project in order to provide the best UX to my users. I solved the performance issue in my component

https://github.com/eregnier/vue2-gmap-custom-marker

by avoiding redraw the marker each time. This is where my math are bad (bad marker positionning on refresh)

You can have a look at the current implementation of this on my dev platform :

https://www.jaccede.com/fr/p/

As you can see, on refresh markers are moving to a bad location for a mico time and then start blink before an ajax query will trigger the entire repaint of all the markers.

Note that I tried to use Vue philosophy in my component and it's usage in the map by avoiding repaint all markers each time (if user drag close to current location, fresh markers from ajax may be the same)

Because of performance issue / bad repaint flow, the render of my component is not optimal at the moment using this technique.

Any note, suggestion , remark would be appreciated if this can lead to a solution that is more generic than my project implementation. thanks.

rmNyro commented 6 years ago

First, some questions: https://github.com/scottdejonge/map-icons -> has been deprecated by its owner, so why bother? See https://github.com/scottdejonge/map-icons/issues/60#issuecomment-265030528 where he says plain svg is probably better. https://www.jaccede.com/fr/p/ -> I only find evidence of angular, did you mix the two? I get it's not open source, but it doesn't prevent you from extracting and rewriting (like I did) key parts so we can take a look and help you.

Anyway I think I've found your problem: https://stackoverflow.com/questions/26316227/how-to-properly-use-complex-svg-image-paths-in-google-maps.

Is this your problem? If so, I'm afraid you cannot have very good performance because you'll need to transform your svg before. Or you would need a service or statically convert your svgs (ie. into images).

If you would have a simple svg you could even use something like marker.icon.url = 'path/to/img.svg' or marker.setIcon({ url: 'path/to/img.svg'}).

My main problem concerning this thread is that you seem to rush your messages and so I have to analyze, make a lot of research, etc... to be able to understand what you want to achieve exactly. Maybe try to clear that point first.

eregnier commented 6 years ago

Hey, Thank you again for the help. Indeed, I wrote the last answer quickly this morning (and half unawake ^^)

However, what I said first in my previous message was : "What I am trying to explain is that I want to display on the map svg icons that are embed in an asset"

I am not sure of many points about how to acheive this, this is why you may feel I post dirty answers (and my english may be too frenchy to be understandable).

So, for the first point about the https://github.com/scottdejonge/map-icons project. What I said is that I found this projects file : https://github.com/scottdejonge/map-icons/blob/master/dist/js/map-icons.js is quite the best code that is the closer to what I wanted to try. Indeed, The component I wrote is a approximative mix of what I found for

and

So My component https://github.com/eregnier/vue2-gmap-custom-marker is so-so well implemented, and the scottdejonge/map-icons repository looked to me containing a better/simpler implementation custom marker using overlay. Except the performances issues that I noticed in this therad and in his projects issues (Thank you again to notice me his project is deprecated and won't receive updates).

I hope this point is clearer to you from my goals to my experiements.

So the second point you found in the https://stackoverflow.com/questions/26316227/how-to-properly-use-complex-svg-image-paths-in-google-maps is entierly relevant, as I was not sure about this topic. This is now clearer about what are the way to go.

Now I think that I am back to the first idea of this thread that is : I think the project xkjyeah/vue-google-maps should provide a similar component of the angular google map one. I would save both my days and other users one, because it would be a simple way to manage custom marker for google map vue. That is why I started my custom marker project and asked for help to improve it (hoping this will be later merged to this project). I think that if it was done properly in the angular gmap project and almost by me, a custom marker component for vue gmap may be a good thing for the project.

Next.

the link to my project is indeed a legacy angular js project. And recently, I started refactore it with vue js using this : https://github.com/ngVue/ngVue. This works fine :D. Finally, the map search tool you see is only a vue component embed in an angular app until the full migration will be complete. So, yes I can show you snippets of my vue map implementation.

Here is the map component simplified:

<template>    
    <gmap-map :center="center" :options="options">

      <gmap-custom-marker :marker="userPosition">
          <svg>
               <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://jaccede.com/img/categories.svg#icon-square_lodging"></use>
          </svg>
      </gmap-custom-marker>

    </gmap-map>
</template>
<script>
    import GmapCustomMarker from 'vue2-gmap-custom-marker';
    export default {
    //...
    }
<script>

where vue2-gmap-custom-marker is my component.

Before posting this message, I think you did not really understood that my icons are in an svg file asset and this is why the topic is not trivial.

One more thing : the solution of svg icons to gmap paths, that is not really possible (In my mind at least) because the gmap icon path system allow only 1 element and 1 fill color at once. And I think this would be pretty bad to add a system in my code that convert my icons with at least 2 shapes and 2 colors each time to many markers with superposition. That's why, again, I am back to the solution with a custom marker component allowing to "transclude" (use slot) html dom.

Let me know If shadow still remains in my explainations. I did my best again.

rmNyro commented 6 years ago

It's way better indeed.

I agree with you now about how to achieve that, I believe hacking into the render class in the marker.js file (https://github.com/xkjyeah/vue-google-maps/blob/vue2/src/components/marker.js) seems to be the way to go. It should be fairly easy to scan the $slot to see if it's an svg or not.

Now as for how to DO it, well that's another question. I'm no expert in vue but I'll try to find the time to investigate, see if I can find a solution based on your code. After that maybe we can find a better implementation but again if you do this on render time that'll be a lot of work (the use of a service worker may be mandatory) on the client side anyway.

eregnier commented 6 years ago

Ok we seem synced :+1:

If you look at my component https://github.com/eregnier/vue2-gmap-custom-marker you may notice that I use a debounce (underscore one) wrapper on the draw method. That allows to process expensive computation once and right time. I put it approximatly, but within all my experiments, I found that the expansive code is not the position computation one, it is the one that generate slot content. So this may be only this critical part that have to be debounced or such.

For information, I first tryied to make a PR to this project in this way, but I did not know vue and gmap enough at the time. Maybe I could do something better now. But, the start of this thread tells that I don't understand why the marker component seems to implement a slot system that does not work properly.

eregnier commented 6 years ago

note before spending too much time on the current marker implementation : I went to mix angular custom marker and google overlay system in my component. that seems to be the only way to display custom DOM elements on the map. The slot system in the existing overlay does not uses overlays, so I think this marker cannot worker properly or be used as cutsom marker element until it manage an underlaying overlay. That is why a dedicated custom marker should be preferable.

eregnier commented 6 years ago

Finally it seems that the marker system in this library only manage images and no custom dom elements with slot. This is not a true issue then, the plugin I made almost do the job. https://www.npmjs.com/package/vue2-gmap-custom-marker