I am trying to implement a pop-up context menu in GoogleMaps. The best of the Google search results I found was this 2012 StackOverflow post
Google Map V3 context menu
(of four examples given at top, two URLs no longer work - but some additional options are given in later comments)
I investigated four code options mentioned there, deciding that the Pearman code, as referenced in
http://googlemapsmania.blogspot.com/2012/04/create-google-maps-context-menu.html
would be best for my case. However, when I implemented it I discovered a problem - which I then determined also occurs in his original example
http://code.martinpearman.co.uk/googlemapsapi/contextmenu/1.0/examples/advanced_example.htm
when one knows what to look for. (This also occurs for a later fork of the original code that I found). All works as expected so long as the map is not moved, with the pop-up menu position being altered by the location of the mouse click such that the entire menu is always visible within the map area. BUT if the map is moved, then the popup can be cutoff, per the bottom left corner in the example below (in which I first moved Norwich into the corner, then right-clicked on it)
Looking into the code, I see it uses GoogleMaps Overlay. Reading up a bit on that, I get the impression that "something" needs to be done/updated when an overlay is moved. But I am beyond my knowledge level and ability to fully understand what I am reading, so am hoping to find some help/insight here. Hoping to figure out a general solution to also help others who implement Pearman's code.
SOLUTION FOUND
Lots of searching and testing and false turns while working on this, but finally got a solution - of course it seems "obvious" after one finally sorts out what is going on.
Short answer: values returned by GM Overlay "fromLatLngToDivPixel" function represent the "GM DIV" and hence location on the GM map whereas "fromLatLngToContainerDiv" values represent the "map_canvas DIV" and hence the observed map boundary. So the actual placing of the menu must be based upon values returned by "fromLatLngToDivPixel" but testing its visibility within the map_canvas DIV must be based upon values from "fromLatLngToContainerDiv". Sometimes values returned by the different functions are identical but sometimes not - the former seems to occur when the map is static whereas the latter can occur when the map is moved/dragged.
Long answer:
The relevant part of the original Pearman code is, where "left" and "top" are later used in CSS styling of the menu, to set its position:
var mousePosition=this.getProjection().fromLatLngToDivPixel(this.position_);
var left=mousePosition.x;
var top=mousePosition.y;
if(mousePosition.x>mapSize.width-menuSize.width-this.pixelOffset.x){
left=left-menuSize.width-this.pixelOffset.x;
} else {
left+=this.pixelOffset.x;
}
if(mousePosition.y>mapSize.height-menuSize.height-this.pixelOffset.y){
top=top-menuSize.height-this.pixelOffset.y;
} else{
top+=this.pixelOffset.y;
}
I found the following helpful comments regarding "fromLatLngToContainerPixel" in How to call fromLatLngToDivPixel in Google Maps API V3? , which led me to my fix
... This works perfectly for me initially, but if I pan the map the
pixel positions are not updated. ...
... using fromLatLngToContainerPixel instead of fromLatLngToDivPixel
solved my issue with pixel positions not updating after panning the map.
Roger Ertesvag
Looking at values returned by "fromLatLngToDivPixel", they were sometimes not valid for the map_canvas DIV containing the map (i.e. were sometime larger than the size of that DIV), whereas "fromLatLngToContainerPixel" DID produce apparently valid values. But the seemingly obvious "fix" of simply replacing "fromLatLngToDivPixel" with "fromLatLngToContainerPixel" often produced much weirdness, with the menu not appearing.
I now gather that the "mousePosition" values returned by "fromLatLngToDivPixel" are valid for the location within the "GoogleMaps DIV" (still unclear exactly what that is) but are not appropriate for the map_canvas DIV. Thus my "fix" was to use values from "fromLatLngToDivPixel" for the "base" menu location but use values from "fromLatLngToContainerPixel" to test and alter its display within the map_canvas DIV.
Specifically, instead of the original I'm now using the following code and it is working successfully.
var mousePosition=this.getProjection().fromLatLngToDivPixel(this.position_);
var left=mousePosition.x;
var top=mousePosition.y;
// my fix below, adjusting location based on nearness of map area boundary
var containerPosition=this.getProjection().fromLatLngToContainerPixel(this.position_);
if(containerPosition.x>mapSize.width-menuSize.width-this.pixelOffset.x){
left=left-menuSize.width-this.pixelOffset.x;
} else {
left+=this.pixelOffset.x;
}
if(containerPosition.y>mapSize.height-menuSize.height-this.pixelOffset.y){
top=top-menuSize.height-this.pixelOffset.y;
} else {
top+=this.pixelOffset.y;
}
PS: as a note to someone wanting to use the Pearman code for their context menus, I am actually using code (now altered) from a fork based upon it
https://github.com/knezmilos13/google-maps-api-contextmenu
The fork adds the ability to have global class names for the menu items, but that is not why I'm using it. For me, the original Pearman code sometimes produced a "sticky" menu, i.e. the menu remained displayed after it should be closed, especially on first usage of a menu, but that behaviour did not occur using the forked code. Don't know the reason for the difference, did not investigate further.
Related
I need to make four rectangles and an arrow at the center that points toward the rectange that is hovered. See https://jsfiddle.net/Lvmf67rm/1/
$(document).ready(function(){
$('.quarter:nth-child(1)').mouseenter(function(){
$('.pointer').css('transform','rotate(-45deg)');
});
$('.quarter:nth-child(2)').mouseenter(function(){
$('.pointer').css('transform','rotate(45deg)');
});
$('.quarter:nth-child(3)').mouseenter(function(){
$('.pointer').css('transform','rotate(-135deg)');
});
$('.quarter:nth-child(4)').mouseenter(function(){
$('.pointer').css('transform','rotate(135deg)');
});
});
There are two issues with what I've made:
If there would be an element before or somewhere in-between the rectangle divs - the arrow would point in the wrong direction (this is because "nth-child()" selects the children regardless of class)
When hovering between 3rd and the 4th rectangles the arrow doesn't go straight to the next block but goes through the first and second first (quite obvious why this happens).
But how to make it right? I'm quite a noob with javascript so this is the best I could do and I ask for your guidance.
P.S. Sorry if I didn't explain it very good, english is not my native.
P.P.S. Sorry, I forgot to mention I can't edit HTML.
Regarding adding elements before or somewhere in-between the rectangle divs, here are some possible solutions:
Best option: Just don't do it. Add other elements outside the container and use CSS positioning to position.
Use :nth-of-type instead of :nth-child
Instead of :nth-child use classes q1, q2...
Regarding arrow direction: See how using -225deg in q4 changes the behaviour:
$('.quarter:nth-child(4)').mouseenter(function(){
console.log($('.pointer').css('transform')); // get prev value
$('.pointer').css('transform','rotate(-225deg)'); // instead of 135deg
});
You can check the previous value to change the degrees dynamically, selecting the best option of the 2 candidates (where the absolute value of previous degrees - new degrees is minimal, adding or subtracting 360 degrees).
I have a Meteor app (source code) which has a stream of entries and new entries are being constantly added on top. I am trying to make it so that if an user scrolls down to a particular entry, that entry should stay visible and not move even when more entries are added on top. Adding and removing entries is animated using Velocity.
I have made code which does that, but it works only in Firefox, while in Chrome it quickly starts jumping around as more entries are coming. Why is that and how could I fix it?
I'm going to post this since it took me a while to figure out. For me it had to do with the Scroll Anchoring feature which was introduced as default in Chrome 56.
The overflow-anchor property enables us to opt out of Scroll Anchoring, which is a browser feature intended to allow content to load above the user's current DOM location without changing the user's location once that content has been fully loaded. Source
You might want to try setting overflow-anchor to none, to opt out of the Scroll Anchoring functionality:
body {
overflow-anchor: none;
}
You can find a demo here, showcasing the difference with and without scroll anchoring.
After you insert the elements at the top, you need to manually re-scroll to the correct position:
function insertNewElementAtTop(parent, elem) {
var scrollTopBeforeInsert = parent.scrollTop;
parent.insertBefore(elem, eParent.firstChild);
parent.scrollTop = scrollTopBeforeInsert + elem.offsetHeight;
}
Update: Fiddle Demonstration -- http://jsfiddle.net/7tfbtso6/2/ -- Most of the settings work in chrome and firefox, but the only one that works in IE is Left-align: 105px. I do have overflow set to hidden on html and body, but this makes no difference. IE will not work if the element is not visible on screen. And overflow: visible on html and body give the effect of auto and no effect on the problem here.
My site uses two contentEditable divs.
#rInput is part of the document.
#rSyntax is part of an iframe under (z-index) #rInput.
In every browser I've tried so far, except IE (I'll get to that in a moment.), I'm able to determine what element is contained within the iframe using elementFromPoint().
In IE's case, this is only possible if they're not overlapping which isn't possible because a secondary purpose, as the name implies, is to provide syntax-highlighting.
The IE IFrame has to be visible, on screen, not obstructed by any objects. I've tried display: none;, visibility: hidden, and pushing it down in a div with overflow: hidden, but all of these attempts cause it not to work. I've also tried setting the height and width to small proportions.
If any of these could work, I could use two copies of rSyntax, one on top (z-index), hidden somehow, for mouse events and one for syntax highlighting.
Most of these solutions work in every browser but IE. The IE box simply demands that it be on top.
"Flickering" it with css (display, visibility, pointer-events) seems awfully hacky (and just plain awful). I haven't really tried to implement it because it seems like a last resort.
The problem is further complicated because I'm trying to capture clicks and mouseovers, for different purposes (clicks for finding content, mouseovers for tooltips--created with a div mimicking attr("title").
I've briefly tried placing the iframe on top (z-index) of the div, but there's no way to intercept the clicks and pass to the lower object because it runs in to the same problem.
Here's the script I'm using to get the objects, partly in case it's useful to anyone.
$(document).on("mousemove", "#rInput", function (e) {
$element = $(document.getElementById('frSyntax').contentDocument.elementFromPoint(e.pageX+$("#rInputContainer").scrollLeft()-10,e.pageY+$("#rInputContainer").scrollTop()-12));
if ($element.is("span") && $element.attr("title") && $element.attr("title").length) {
$("#syntip").text($element.attr("title"));
$("#syntip").css({"top": e.pageY+10, "left": e.pageX, "display": "inline-block"});
} else {
$("#syntip").hide();
}
});
I have considered transparency, and that works for this element, because it's small, but I use a similar setup with a large element that takes up more than 50% of the screen, there would be problems.
After many frustrating efforts, I concluded that pushing the top (z-index-wise) element offscreen was the only solution for IE/Edge. Flickering it with display: none causes some properties I needed, like width, to not be accurate.
Just make sure you push it farther than the element will ever be. My application is sidescrolling so I merely needed to place the css top to something like 2000.
So I have a "cursor" object created like so:
var cursor=document.createElement('span');
cursor.id="currentCursor";
cursor.innerHTML="|";
cursor.style.fontWeight="bold";
cursor.style.position = 'absolute';
cursor.style.marginLeft="-1px";
Then I add it to the page where someone clicks with this:
var selection = window.getSelection();
var currentRange = selection.getRangeAt(0);
currentRange.insertNode(cursor);
The problem I'm running into is in certain places (mainly end of lines) if the cursor object is added it creates a line break before the object. Using insertNode to move it to another area removes the line break. Also if I set the display to "none", wait for a few seconds and then set it back to "inline" the line break is removed.
This seems like maybe a browser bug in adding absolute elements, but I was wondering if someone had a workaround. I've tried setting the width to 0px but it has no effect.
Update
So if I change the cursor to
cursor.style.position = 'static';
It doesn't have random line breaks. However this causes space to be created around the element. Any way to not allow elements to create space around them?
Update 2
Added a fiddle to show the problem:
http://jsfiddle.net/Mctittles/pSg2D/1/
Original code is a bit large but I slimmed it down to highlight this problem.
If you click at the end of the smiley face and then type it causes line 33 to trigger creating a new text node. After typing a couple letters you'll see the cursor object is forced to the next line. Clicking somewhere else to move it makes the lines merge again.
If you un-comment lines 38 and 40 you'll see what I was talking about with making it initially display:none and changing it later. This time it doesn't cause a line break.
I took out some cross-browser code for fiddler, so this might only work in Chrome
However [position:static] causes space to be created around the element.
No, it doesn’t cause it – there is no actual space created “around it”, it’s just the display width of a character plus spacing in the used font, and that gives the span element itself a width that is more than the | character itself. But when you position the element absolutely, you don’t notice that, because it is taken out of the flow, so it doesn’t push the following characters to the right.
My workaround proposal: Don’t put | into the span as innerHTML, but leave it empty – and then implement the line by giving the element a border-left:1px solid. Remove position:absolute, so that it defaults to static.
Then you might probably not like the height your cursor is getting with that – but that can be fixed as well, by setting display to inline-block, and giving it a height as well.
Here, see how you like ’dem apples: http://jsfiddle.net/pSg2D/9/
You should use CSS instead. Using z-index and maybe even float would (atleast should) fix this.
Edit: Always make sure no other styles make it break line!
I use this code:
$("img.cloudcarousel").each(function(i, e){
coords[i] = $(e).offset();
});
to save the position of the images (and it works).
Then I animate them and move them.
Then I use this code:
$("img.cloudcarousel").each(function(i, e){
$(e).animate({top:coords[i].top, left:coords[i].left}, 1000);
});
to animate them back to where they belong.
in IE (at least 8) it works fine but in Chrome and Firefox it animates 40-50 pixels too much to the left and down (like its over-animating).
dont ask me how I discovered this: when i use mousewheel over them they go to where they belong!
I guess it's somehow related to the buildup of the animation queue, however I only use four images and it doesn't fix on its own after x time, only on the mousewheel stuff.
edit : added to jsfiddle.net
I'm not really sure how that site works but I added my HTML and JS into it:
http://jsfiddle.net/3wqYg/
The $(e).offset() is not returning the values that is currently defined in your fiddle. I have not looked up the definition of offset but if you output the coords, you will see it is not the same as in the code
Edit: I see the problem see offset() http://api.jquery.com/offset/ it returns the x, y relative to document BUT when you animated it back it is relative to the parent element (default behavior). So in the doc it mentions using position(). that is relative to the parent element, I have not tried it but if you use that it should work.
Final Edit: yep works fine with position see http://jsfiddle.net/3wqYg/1/ you must copy it into a test page because it does not animate on fiddle