Popover overlay in OpenLayers 3 does not extend beyond view - javascript

In the OpenLayers overlay example:
http://openlayers.org/en/v3.11.2/examples/overlay.html
If you click near the top of map most of the overlay is hidden. Is there a CSS trick, or an OpenLayers setting (I do not want to use the autoPan, which doesn't seem to work for popovers anyway), that will enable the entire popover to be shown even if it extends beyond the map view?
Here's a screenshot that illustrates the problem.

autoPan does work for popups, see here: http://openlayers.org/en/v3.11.2/examples/popup.html
However, I also had some trouble with autoPan so I didi it like this (Fiddle demo):
// move map if popop sticks out of map area:
var extent = map.getView().calculateExtent(map.getSize());
var center = map.getView().getCenter();
var pixelPosition = map.getPixelFromCoordinate([ coordinate[0], coordinate[1] ]);
var mapWidth = $("#map").width();
var mapHeight = $("#map").height();
var popoverHeight = $("#popup").height();
var popoverWidth = $("#popup").width();
var thresholdTop = popoverHeight+50;
var thresholdBottom = mapHeight;
var thresholdLeft = popoverWidth/2-80;
var thresholdRight = mapWidth-popoverWidth/2-130;
if(pixelPosition[0] < thresholdLeft || pixelPosition[0] > thresholdRight || pixelPosition[1]<thresholdTop || pixelPosition[1]>thresholdBottom) {
if(pixelPosition[0] < thresholdLeft) {
var newX = pixelPosition[0]+(thresholdLeft-pixelPosition[0]);
} else if(pixelPosition[0] > thresholdRight) {
var newX = pixelPosition[0]-(pixelPosition[0]-thresholdRight);
} else {
var newX = pixelPosition[0];
}
if(pixelPosition[1]<thresholdTop) {
var newY = pixelPosition[1]+(thresholdTop-pixelPosition[1]);
} else if(pixelPosition[1]>thresholdBottom) {
var newY = pixelPosition[1]-(pixelPosition[1]-thresholdBottom);
} else {
var newY = pixelPosition[1];
}
newCoordinate = map.getCoordinateFromPixel([newX, newY]);
newCenter = [(center[0]-(newCoordinate[0]-coordinate[0])), (center[1]-(newCoordinate[1]-coordinate[1])) ]
map.getView().setCenter(newCenter);
}

I added this code to the Popover Official Example in this fiddle demo:
// get DOM element generated by Bootstrap
var bs_element = document.getElementById(element.getAttribute('aria-describedby'));
var offset_height = 10;
// get computed popup height and add some offset
var popup_height = bs_element.offsetHeight + offset_height;
var clicked_pixel = evt.pixel;
// how much space (height) left between clicked pixel and top
var height_left = clicked_pixel[1] - popup_height;
var view = map.getView();
// get the actual center
var center = view.getCenter();
if (height_left < 0) {
var center_px = map.getPixelFromCoordinate(center);
var new_center_px = [
center_px[0],
center_px[1] + height_left
];
map.beforeRender(ol.animation.pan({
source: center,
start: Date.now(),
duration: 300
}));
view.setCenter(map.getCoordinateFromPixel(new_center_px));
}

To make the popup always appear inside the map view, I reversed the ol3 autopan function
So that it the popup is offset from the feature towards the view, instead of panning the view.
I am not sure why so many ol3 fiddles are not loading the map anymore.
http://jsfiddle.net/bunjil/L6rztwj8/48/
var getOverlayOffsets = function(mapInstance, overlay) {
const overlayRect = overlay.getElement().getBoundingClientRect();
const mapRect = mapInstance.getTargetElement().getBoundingClientRect();
const margin = 15;
// if (!ol.extent.containsExtent(mapRect, overlayRect)) //could use, but need to convert rect to extent
const offsetLeft = overlayRect.left - mapRect.left;
const offsetRight = mapRect.right - overlayRect.right;
const offsetTop = overlayRect.top - mapRect.top;
const offsetBottom = mapRect.bottom - overlayRect.bottom;
console.log('offsets', offsetLeft, offsetRight, offsetTop, offsetBottom);
const delta = [0, 0];
if (offsetLeft < 0) {
// move overlay to the right
delta[0] = margin - offsetLeft;
} else if (offsetRight < 0) {
// move overlay to the left
delta[0] = -(Math.abs(offsetRight) + margin);
}
if (offsetTop < 0) {
// will change the positioning instead of the offset to move overlay down.
delta[1] = margin - offsetTop;
} else if (offsetBottom < 0) {
// move overlay up - never happens if bottome-center is default.
delta[1] = -(Math.abs(offsetBottom) + margin);
}
return (delta);
};
/**
* Add a click handler to the map to render the popup.
*/
map.on('click', function(evt) {
var coordinate = evt.coordinate;
var hdms = ol.coordinate.toStringHDMS(ol.proj.transform(
coordinate, 'EPSG:3857', 'EPSG:4326'));
content.innerHTML = '<p>You clicked here:</p><code>' + hdms +
'</code>';
//overlay.setPosition(coordinate);
overlay.setOffset([0, 0]); // restore default
overlay.setPositioning('bottom-right'); // restore default
//overlay.set('autopan', true, false); //only need to do once.
overlay.setPosition(coordinate);
const delta = getOverlayOffsets(map, overlay);
if (delta[1] > 0) {
overlay.setPositioning('bottom-center');
}
overlay.setOffset(delta);
})
In this fiddle, the setPositioning() isn't working, so when you click near the top, the popup is under your mouse - it would be better to setPositioning('bottom-center');
automove would be a good feature to complement autopan.

In case of popover where "autoPan" option is not available you have to check extent's limits (top/bottom/right - left is skipped since popover is spawned on the center right of feature). So extending previous answer of Jonatas Walker a bit:
var bs_element = $('.popover');
var popup_height = bs_element.height();
var popup_width = bs_element.width();
var clicked_pixel = evt.pixel;
var view = map.getView();
var center = view.getCenter();
var height_left = clicked_pixel[1] - popup_height / 2; // from top
var height_left2 = clicked_pixel[1] + popup_height / 2; // from bottom
var width_left = clicked_pixel[0] + popup_width; // from right
var center_px = map.getPixelFromCoordinate(center);
var new_center_px = center_px;
var needs_recenter = false;
if (height_left2 > $("#map").height()) {
new_center_px[1] = height_left2 - center_px[1] + 30;
needs_recenter = true;
}
else if (height_left < 0) {
new_center_px[1] = center_px[1] + height_left;
needs_recenter = true;
}
if (width_left > $("#map").width()) {
new_center_px[0] = width_left - center_px[0] + 30;
needs_recenter = true;
}
if (needs_recenter)
view.setCenter(map.getCoordinateFromPixel(new_center_px));

Related

How do I remove setInterval from progress bar in Javascript and make appear the VerticalBar instantaneously?

I have the classical Progress bar in Javascript. I Would like to simply place the vertical bar without seeing it moving on the screen.
function progressbar() {
var vertical_bar2 = document.querySelector("#P5 .vl5");
var element = document.getElementById("myprogressBar");
var ValueSet = 25
var width = 0;
document.getElementById("vl5").style.display='';
document.getElementById("value2").style.display='';
document.getElementById("value1").style.display='';
var identity = setInterval(scene, 10);
function scene() {
if (width >= ValueSet) {
clearInterval(identity);
} else {
width++;
vertical_bar2.style.left = `${width}%`;
document.getElementById("value2").innerHTML = ValueSet
}
}
}
I am trying the following script but it is not working
function progressbar() {
var vertical_bar2 = document.querySelector("#P5 .vl5");
var element = document.getElementById("progressBar");
var CE = 25
var width = 0;
document.getElementById("vl5").style.display='';
document.getElementById("Value2").style.display='';
document.getElementById("Value1").style.display='';
vertical_bar2.style.left = 25;
document.getElementById("Value").innerHTML = CE
}
Based on the code your provided, it looks like you just need to set a unit for the left property of your bar. Currently you are only setting a value (25), but without a unit (%, px, em, etc.) it will not apply anything.
vertical_bar2.style.left = "25%";
or
vertical_bar2.style.left = `${CE}%`;

Make a multiple objects with javascript and png

I'm trying to get a spaceship animation scene with a group of comets going down.
//Create a comet div with img attached to it
var cometScene = function(spaceNo){
var b = document.createElement('div');
b.id = 'cometio';
var cometImage = document.createElement('img');
cometImage.setAttribute('src', 'images/comet1.png');
b.appendChild(cometImage);
document.getElementById('wrap').appendChild(b);
}
//Comet move
function cometMove(){
var comet = document.getElementById('cometio');
var pos = 0;
var interval = setInterval(scene, 3);
function scene(){
if (pos === 1000){
clearInterval(interval);
} else {
pos++;
comet.style.top = pos + 'px';
comet.style.left = pos + 'px';
}
}
setInterval(scene, 3)
}
But when I call a function cometScene(3) I'm not getting 3 similar objects. Also how these objects can be allocated across the whole screen as this is just a single div.
function main(){
var w = document.createElement('div');
w.id = 'wrap';
document.querySelector('body').appendChild(w);
astronautScene();
cometScene();
shaceshipScene();
cometMove();
astronautMove();
}
This it what I would do:
Give the comets a class instead of an id, because there can be more of them.
Because there can be multiple use a loop to iterate through them
To give them the ability to move freely, they need to have position:absolute or something similiar
Don't use the same variable for the position of all comets, because they could be in different positions
To get the current position just parse the currect top and left value to a Number
//Create a comet div with img attached to it
var cometScene = function(spaceNo) {
var b = document.createElement('div');
b.className = 'cometio';
var cometImage = document.createElement('img');
cometImage.setAttribute('src', 'images/comet1.png');
b.appendChild(cometImage);
document.getElementById('wrap').appendChild(b);
}
//Comet move
function cometMove() {
var comets = document.getElementsByClassName('cometio');
for (let i = 0; i < comets.length; i++) {
const comet = comets[i];
comet.style.top = "0px";
comet.style.left = "0px";
comet.style.position = "absolute";
var interval = setInterval(scene, 3);
function scene() {
let x = parseInt(comet.style.left);
let y = parseInt(comet.style.top);
if (x === 1000) {
clearInterval(interval);
} else {
comet.style.top = (1 + x) + 'px';
comet.style.left = (1 + y) + 'px';
}
}
}
//setInterval(scene, 3)don't start the interval twice
}
function main() {
var w = document.createElement('div');
w.id = 'wrap';
document.querySelector('body').appendChild(w);
//astronautScene();
cometScene();
//shaceshipScene();
cometMove();
//astronautMove();
}
main();

adding text to overlay in javascript

I've used some source for a transparent overlay in JavaScript:
function grayOut(vis, options)
{
var options = options || {};
var zindex = 50;
var opacity = 70;
var opaque = (opacity / 100);
var bgcolor = options.bgcolor || '#000000';
var dark=document.getElementById('darkenScreenObject');
if (!dark) {
// The dark layer doesn't exist, it's never been created. So we'll
// create it here and apply some basic styles.
// If you are getting errors in IE see: http://support.microsoft.com/default.aspx/kb/927917
var tbody = document.getElementsByTagName("body")[0];
var tnode = document.createElement('div'); // Create the layer.
tnode.style.position='absolute'; // Position absolutely
tnode.style.top='0px'; // In the top
tnode.style.left='0px'; // Left corner of the page
tnode.style.display='none'; // Start out Hidden
tnode.id='darkenScreenObject'; // Name it so we can find it later
tbody.appendChild(tnode);
/*
var pTag = document.createElement("P");
var txtProcessing = document.createTextNode("Processing GIF...");
tnode.appendChild(txtProcessing);
*/
}
if (vis)
{
// Calculate the page width and height
if( document.body && ( document.body.scrollWidth || document.body.scrollHeight ) )
{
var pageWidth = document.body.scrollWidth+'px';
var pageHeight = document.body.scrollHeight+'px';
}
else if( document.body.offsetWidth )
{
var pageWidth = document.body.offsetWidth+'px';
var pageHeight = document.body.offsetHeight+'px';
}
else
{
var pageWidth='100%';
var pageHeight='100%';
}
//set the shader to cover the entire page and make it visible.
dark.style.opacity=opaque;
dark.style.MozOpacity=opaque;
dark.style.filter='alpha(opacity='+opacity+')';
dark.style.zIndex=zindex;
dark.style.backgroundColor=bgcolor;
dark.style.width= pageWidth;
dark.style.height= pageHeight;
dark.style.display='block';
var txt = document.createTextNode("This text was added.");
dark.appendChild(txt);
}
else
{
dark.style.display='none';
}
}
My problem is I'm trying to get some text to show up on the transparent layer but I can't get it to work. Any thoughts?
Your text node is created on overlay but is invisible cause of text color.
check Fiddle where text color is set to red.
dark.style.color = 'red';

Particles with CSS and JavaScript

I'm trying to create something like a very simple particle system. No physics required at this point, just divs that are animated to look like bubbles, or bees, or whatever. The code below creates the divs and through CSS I can make them change position, floating upwards. But I can't seem to workout how to destroy particles. Each particle does it's motion and then returns back to it's original point. I would prefer if it was removed completely.
Thank you.
/* ==================== PARTICLES CONTROLLER ==================== */
/**
* Particle controller interates through all elements with
* CSS class name PARTICLE_CSS and when found a ParticleController is instantiated
* for each of the elements.
*/
function ParticleBaseController(){
var ParticleContainer = document.querySelectorAll(PARTICLE_CSS),
ParticleContainerLength = ParticleContainer.length;
for (var i = ParticleContainerLength - 1; i >= 0; i--){
new ParticleController(ParticleContainer[i]);
};
};
function ParticleController(element) {
var particleElement, fragment = document.createDocumentFragment();
var numberOfParticles = 1;
for (var i = 0; i < numberOfParticles; i ++) {
particleElement = document.createElement("div");
particleElement.addClassName(PARTICLE_ELEMENT_CSS_CLASS);
var Ypos = Math.floor((Math.random()*200)+1);
var Xpos = Math.floor((Math.random()*200)+1);
particleElement.style.top = Ypos + "px";
particleElement.style.left = Xpos + "px";
fragment.appendChild(particleElement);
}
element.appendChild(fragment);
setTimeout(ParticleBaseController, 3000);
};
This worked for me. I am guessing that the way it works is that particles are only appended to the container as long as there are fewer than 15. Although I do not know how they are actually destroyed. But on screen I can only ever see 15 particles at a time, or however many I set then number to.
/* ==================== PARTICLES CONTROLLER ==================== */
const NumberOfParticles = 15;
function smoking()
{
var container = document.getElementById('particleContainer');
for (var i = 0; i < NumberOfParticles; i++)
{
container.appendChild(createParticle());
}
}
function randomFloat(low, high)
{
return low + Math.random() * (high - low);
}
function createParticle()
{
var particleDiv = document.createElement('div');
particleDiv.style.top = "-300px";
particleDiv.style.left = "200px"
particleDiv.style.webkitAnimationName = 'smoke-rise, smoke-fade';
var SmokeDuration = randomFloat(5, 10)+"s";
var FadeDuration = randomFloat(4, 11)+"s";
var SmokeDelay = randomFloat(0, 5)+"s";
var FadeDelay = randomFloat(2, 9)+"s";
particleDiv.style.webkitAnimationDuration = SmokeDuration + ', ' + FadeDuration;
particleDiv.style.webkitAnimationDelay = SmokeDelay + ', ' + FadeDelay;
return particleDiv;
}
window.addEventListener("DOMContentLoaded", smoking, false);

Javascript News Scroller

see the news scroller on the top of this site
http://track.dc.gov/Agency/DH0
Any idea what library/functions this site uses to implment such a smooth scroller?
They have a very nicely formatted block of code you can study. Open your favorite JS debugger when you visit the site, wait for everything to get moving, and then press "Break All" or the equivalent in your debugger. You'll see something like the following:
Dashboard.UI.EndlessLine = function() {
var me = this;
me.jq = $(me);
me.classNames = { CONTAINER: "uiEndless", VIEW: "uiEndlessView", CANVAS: "uiEndlessCanvas", TILE: "uiEndlessTile" };
var canvas = null;
var view = null;
var tiles = null;
var x = 0;
var xx = 0;
var canvasWidth = 0;
var step = 1;
var delay = 40;
me.initialize = function(container, data, handler) {
required(container, "container");
required(data, "data");
required(handler, "handler");
container.addClass(me.classNames.CONTAINER);
view = newDiv(me.classNames.VIEW);
canvas = newDiv(me.classNames.CANVAS);
view.append(canvas);
container.append(view);
x = 0;
xx = 0;
canvasWidth = 0;
tiles = me.populateTiles(data, handler);
container.click(function() {
if (me.started()) me.stop(); else me.start();
});
};
me._resize = function(size) {
};
var moveId = 0;
me.start = function() {
me.stop();
me.tick();
}
me.stop = function() {
if (moveId > 0) clearTimeout(moveId);
moveId = 0;
}
me.started = function() {
return moveId > 0;
};
me.tick = function() {
var tile = tiles.current();
var width = tile.calculatedWidth;
if (x < width - step) {
x += step;
} else {
x = 0;
tile.css("left", canvasWidth + "px");
if (tiles.advance()) {
xx = 0;
canvasWidth = 0;
do {
current = tiles.current();
width = current.calculatedWidth;
current[0].style.left = canvasWidth + "px";
canvasWidth += width;
} while (!tiles.advance());
} else {
canvasWidth += width;
}
}
canvas[0].style.left = -(xx) + "px";
xx += step;
moveId = setTimeout(me.tick, delay);
}
me.populateTiles = function(data, handler) {
var tiles = new Dashboard.Core.List();
var viewWidth = view.contentWidth();
var maxHeight = 0;
each(data, function() {
var tile = newDiv(me.classNames.TILE);
handler.call(this, tile);
tile.css({ left: canvasWidth + "px", top: 0 });
canvas.append(tile);
var width = tile.outerWidth();
var height = tile.outerHeight();
if (maxHeight < height) maxHeight = height;
tile.calculatedWidth = width;
canvasWidth += width; // getting width may only be done after the element is attached to DOM
tiles.append(tile);
view.height(height);
});
return tiles.createCycle();
}
}
I'm impressed -- everything looks professional and nicely namespaced.
Update: If you want an explanation of how it works, focus on the tick method defined above. Glossing over all the details (cause I haven't really studied it myself), it calculates a step size, moves the message element to the left by the some amount, and schedules the next tick call for 40 milliseconds in the future.
jQuery enthusiast, Remy Sharp, has his own Marquee Plugin that you can implement pretty easily. You can gather deeper details of it on his blog or by visiting the demo page.
For Mootools users, there's Mooquee.
You can also view the actual code for this example online at http://track.dc.gov/Resource/Script/ - do a search for "uiEndless" to find the target-scripting.

Categories