Keep one element on top of another - javascript

I have an HTML/DOM element that I need to stay positioned above another element, regardless of where and when the target element moves.
For various (and unfortunate) reasons, I can't do this through simple CSS relative positioning. The element that I want to reposition can't be an immediate child of the parent of the target element. (It needs to be several layers up in fact, though both elements do share a common ancestor.)
I can position the element over the target element easily using jQuery's offset() functions. However, if the target element moves, the repositioned element doesn't follow. Changing the browser dimensions is one way (and the primary way I care about) this can happen; the layout changes which causes the target element's offset to change.
Here's the rough structure of my document:
<div id="common-ancestor">
<div id="to-reposition-container">
<div id="to-reposition"></div>
</div>
<div id="some-stuff-in-between"></div>
<div id="target-container">
<div id="target"></div>
<div>
</div>
I want #to-reposition to be visually placed over top of #target without changing the DOM tree. I cannot absolutely position #target outside of the natural bounds of #target-container.
I'm open to solutions that use CSS, JavaScript, and/or jQuery.

Repositioning the element the same way you did originally within a window resize event handler should work, but it certainly won't handle other cases, such as font size changes and scrolling. I don't think there's any sort of layout changing event, so if you want to catch all cases of the element's moving, you'd unfortunately have to constantly reposition in a timer event.

It's not pretty but it'll do the trick:
$(window).bind('resize',function() {/* reposition using offset */});
The better way is to use css (since that would also work in none-resize scenarios)
With the supplied markup i'd suggest setting #common-ancestor { position: relative; } and then absolutely position #to-reposition-container & #target-container on top of eachother, from there it should not be a problem for you.

Related

Event listener does not work when image element blocks it [duplicate]

I have a div that has background:transparent, along with border. Underneath this div, I have more elements.
Currently, I'm able to click the underlying elements when I click outside of the overlay div. However, I'm unable to click the underlying elements when clicking directly on the overlay div.
I want to be able to click through this div so that I can click on the underlying elements.
Yes, you CAN do this.
Using pointer-events: none along with CSS conditional statements for IE11 (does not work in IE10 or below), you can get a cross browser compatible solution for this problem.
Using AlphaImageLoader, you can even put transparent .PNG/.GIFs in the overlay div and have clicks flow through to elements underneath.
CSS:
pointer-events: none;
background: url('your_transparent.png');
IE11 conditional:
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='your_transparent.png', sizingMethod='scale');
background: none !important;
Here is a basic example page with all the code.
Yes, you CAN force overlapping layers to pass through (ignore) click events.
PLUS you CAN have specific children excluded from this behavior...
You can do this, using pointer-events
pointer-events influences the reaction to click-, tap-, scroll- und hover events.
In a layer that should ignore / pass-through mentioned events you set
pointer-events: none;
Children of that unresponsive layer that need to react mouse / tap events again need:
pointer-events: auto;
That second part is very helpful if you work with multiple overlapping div layers (probably some parents being transparent), where you need to be able to click on child elements and only that child elements.
Example usage:
.parent {
pointer-events:none;
}
.child {
pointer-events:auto;
}
<div class="parent">
I'm unresponsive
I'm clickable again, wohoo !
</div>
Allowing the user to click through a div to the underlying element depends on the browser. All modern browsers, including Firefox, Chrome, Safari, and Opera, understand pointer-events:none.
For IE, it depends on the background. If the background is transparent, clickthrough works without you needing to do anything. On the other hand, for something like background:white; opacity:0; filter:Alpha(opacity=0);, IE needs manual event forwarding.
See a JSFiddle test and CanIUse pointer events.
I'm adding this answer because I didn’t see it here in full. I was able to do this using elementFromPoint. So basically:
attach a click to the div you want to be clicked through
hide it
determine what element the pointer is on
fire the click on the element there.
var range-selector= $("")
.css("position", "absolute").addClass("range-selector")
.appendTo("")
.click(function(e) {
_range-selector.hide();
$(document.elementFromPoint(e.clientX,e.clientY)).trigger("click");
});
In my case the overlaying div is absolutely positioned—I am not sure if this makes a difference. This works on IE8/9, Safari Chrome and Firefox at least.
Hide overlaying the element
Determine cursor coordinates
Get element on those coordinates
Trigger click on element
Show overlaying element again
$('#elementontop').click(e => {
$('#elementontop').hide();
$(document.elementFromPoint(e.clientX, e.clientY)).trigger("click");
$('#elementontop').show();
});
I needed to do this and decided to take this route:
$('.overlay').click(function(e){
var left = $(window).scrollLeft();
var top = $(window).scrollTop();
//hide the overlay for now so the document can find the underlying elements
$(this).css('display','none');
//use the current scroll position to deduct from the click position
$(document.elementFromPoint(e.pageX-left, e.pageY-top)).click();
//show the overlay again
$(this).css('display','block');
});
I currently work with canvas speech balloons. But because the balloon with the pointer is wrapped in a div, some links under it aren't click able anymore. I cant use extjs in this case.
See basic example for my speech balloon tutorial requires HTML5
So I decided to collect all link coordinates from inside the balloons in an array.
var clickarray=[];
function getcoo(thatdiv){
thatdiv.find(".link").each(function(){
var offset=$(this).offset();
clickarray.unshift([(offset.left),
(offset.top),
(offset.left+$(this).width()),
(offset.top+$(this).height()),
($(this).attr('name')),
1]);
});
}
I call this function on each (new) balloon. It grabs the coordinates of the left/top and right/down corners of a link.class - additionally the name attribute for what to do if someone clicks in that coordinates and I loved to set a 1 which means that it wasn't clicked jet. And unshift this array to the clickarray. You could use push too.
To work with that array:
$("body").click(function(event){
event.preventDefault();//if it is a a-tag
var x=event.pageX;
var y=event.pageY;
var job="";
for(var i in clickarray){
if(x>=clickarray[i][0] && x<=clickarray[i][2] && y>=clickarray[i][1] && y<=clickarray[i][3] && clickarray[i][5]==1){
job=clickarray[i][4];
clickarray[i][5]=0;//set to allready clicked
break;
}
}
if(job.length>0){
// --do some thing with the job --
}
});
This function proofs the coordinates of a body click event or whether it was already clicked and returns the name attribute. I think it is not necessary to go deeper, but you see it is not that complicate.
Hope in was enlish...
Another idea to try (situationally) would be to:
Put the content you want in a div;
Put the non-clicking overlay over the entire page with a z-index higher,
make another cropped copy of the original div
overlay and abs position the copy div in the same place as the original content you want to be clickable with an even higher z-index?
Any thoughts?
I think the event.stopPropagation(); should be mentioned here as well. Add this to the Click function of your button.
Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
Just wrap a tag around all the HTML extract, for example
<a href="/categories/1">
<img alt="test1" class="img-responsive" src="/assets/photo.jpg" />
<div class="caption bg-orange">
<h2>
test1
</h2>
</div>
</a>
in my example my caption class has hover effects, that with pointer-events:none; you just will lose
wrapping the content will keep your hover effects and you can click in all the picture, div included, regards!
An easier way would be to inline the transparent background image using Data URIs as follows:
.click-through {
pointer-events: none;
background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
}
I think that you can consider changing your markup. If I am not wrong, you'd like to put an invisible layer above the document and your invisible markup may be preceding your document image (is this correct?).
Instead, I propose that you put the invisible right after the document image but changing the position to absolute.
Notice that you need a parent element to have position: relative and then you will be able to use this idea. Otherwise your absolute layer will be placed just in the top left corner.
An absolute position element is positioned relative to the first parent
element that has a position other than static.
If no such element is found, the containing block is html
Hope this helps. See here for more information about CSS positioning.
You can place an AP overlay like...
#overlay {
position: absolute;
top: -79px;
left: -60px;
height: 80px;
width: 380px;
z-index: 2;
background: url(fake.gif);
}
<div id="overlay"></div>
just put it over where you dont want ie cliked. Works in all.
This is not a precise answer for the question but may help in finding a workaround for it.
I had an image I was hiding on page load and displaying when waiting on an AJAX call then hiding again however...
I found the only way to display my image when loading the page then make it disappear and be able to click things where the image was located before hiding it was to put the image into a DIV, make the size of the DIV 10x10 pixels or small enough to prevent it causing an issue then hiding the containing div. This allowed the image to overflow the div while visible and when the div was hidden, only the divs area was affected by inability to click objects beneath and not the whole size of the image the DIV contained and was displaying.
I tried all the methods to hide the image including CSS display=none/block, opacity=0, hiding the image with hidden=true. All of them resulted in my image being hidden but the area where it was displayed to act like there was a cover over the stuff underneath so clicks and so on wouldn't act on the underlying objects. Once the image was inside a tiny DIV and I hid the tiny DIV, the entire area occupied by the image was clear and only the tiny area under the DIV I hid was affected but as I made it small enough (10x10 pixels), the issue was fixed (sort of).
I found this to be a dirty workaround for what should be a simple issue but I was not able to find any way to hide the object in its native format without a container. My object was in the form of etc. If anyone has a better way, please let me know.
I couldn't always use pointer-events: none in my scenario, because I wanted both the overlay and the underlying element(s) to be clickable / selectable.
The DOM structure looked like this:
<div id="outerElement">
<div id="canvas-wrapper">
<canvas id="overlay"></canvas>
</div>
<!-- Omitted: element(s) behind canvas that should still be selectable -->
</div>
(The outerElement, canvas-wrapper and canvas elements have the same size.)
To make the elements behind the canvas act normally (e.g. selectable, editable), I used the following code:
canvasWrapper.style.pointerEvents = 'none';
outerElement.addEventListener('mousedown', event => {
const clickedOnElementInCanvas = yourCheck // TODO: check if the event *would* click a canvas element.
if (!clickedOnElementInCanvas) {
// if necessary, add logic to deselect your canvas elements ...
wrapper.style.pointerEvents = 'none';
return true;
}
// Check if we emitted the event ourselves (avoid endless loop)
if (event.isTrusted) {
// Manually forward element to the canvas
const mouseEvent = new MouseEvent(event.type, event);
canvas.dispatchEvent(mouseEvent);
mouseEvent.stopPropagation();
}
return true;
});
Some canvas objects also came with input fields, so I had to allow keyboard events, too.
To do this, I had to update the pointerEvents property based on whether a canvas input field was currently focused or not:
onCanvasModified(canvas, () => {
const inputFieldInCanvasActive = // TODO: Check if an input field of the canvas is active.
wrapper.style.pointerEvents = inputFieldInCanvasActive ? 'auto' : 'none';
});
it doesn't work that way. the work around is to manually check the coordinates of the mouse click against the area occupied by each element.
area occupied by an element can found found by 1. getting the location of the element with respect to the top left of the page, and 2. the width and the height. a library like jQuery makes this pretty simple, although it can be done in plain js. adding an event handler for mousemove on the document object will provide continuous updates of the mouse position from the top and left of the page. deciding if the mouse is over any given object consists of checking if the mouse position is between the left, right, top and bottom edges of an element.
Nope, you can't click ‘through’ an element. You can get the co-ordinates of the click and try to work out what element was underneath the clicked element, but this is really tedious for browsers that don't have document.elementFromPoint. Then you still have to emulate the default action of clicking, which isn't necessarily trivial depending on what elements you have under there.
Since you've got a fully-transparent window area, you'll probably be better off implementing it as separate border elements around the outside, leaving the centre area free of obstruction so you can really just click straight through.

Div resizing with mouse expands too fast

For fun, I am creating a widget that can be toggled to use vertical typing (e.g. for Japanese). The vertical part is being done with a div that is contenteditable. At the moment, you can see both the textarea and the div, and watch their content change in sync. The checkbox does nothing yet.
My problem is that the svg that is the "handle" to drag and resize was inside the editable div, and this would get messed up if the user cut from or pasted into the div. So that's the real problem I'm solving, and if there's a better fix, let me know. In an attempt to fix this, I am putting the editable div within another div that has the svg, in order that the internal editable div will not affect it.
After doing this, the resize functionality does not work correctly. After clicking down on the handle, the div resizes extremely fast downwards (it's height growing).
Here is what I was trying to do "working": https://jsfiddle.net/m4Ljuzyn/167/
And here is the one where the resize is broken after I changed the layout a bit: https://jsfiddle.net/m4Ljuzyn/190/
I am using pure JavaScript only.
The problem comes from the fact that offsetLeft and offsetTop are relative to the element's parent. You're using that relative value with the mouse event's clientX/Y value and it's producing undesired results.
Take advantage of getBoundingClientRect for proper coordinates in your mousemove handler.
var bounds = verticalTextarea.getBoundingClientRect();
verticalTextarea.style.width = (evt.clientX - bounds.left) + "px";
verticalTextarea.style.height = (evt.clientY - bounds.top) + "px";
Be warned: getBoundingClientRect is an expensive calculation, and may negatively affect performance.
Update: Added Forked JSFiddle
https://jsfiddle.net/csoh7xzv/

Saving off an element's CSS and reapply later

Is it possible to save off all CSS that is 'currently' applied to an element, then later reapply it? I am working on a sticking table header, and when I i change position:fixed it loses all the applied styles. I currently save off the column widths and reapply to the table header with:
$('#tableHeader').css({
position:'fixed',
width:$('#tablePanel').width(),
top:$('#top').height(),
});
$('.column1Value').width(col1Width);
$('#col1').width(col1Width);
$('.column2Value').width(col2Width);
$('#col2').width(col2Width);
$('.column3Value').width(col3Width);
$('#col3').width(col3Width);
$('.column4Value').width(col4Width);
$('#col4').width(col4Width);
$('.column5Value').width(col5Width);
$('#col5').width(col5Width);
$('.column6Value').width(col6Width);
$('#col6').width(col6Width);
$('.column7Value').width(col7Width);
$('#col7').width(col7Width);
This make the columns the correct size and line up closely, but there is extra padding or margin being applied from somewhere I can't completely figure out (bootstrap probably), and this makes the headers and columns not line up. I was hoping for something like:
var savedCSS = $('#table').css(); and retrieve it like $('#table').css(savedCSS)
You could save off the individual styles that you are interested in one by one and then re-apply them later using the jQuery("selector").css("styleName") method that you alluded to, but I don't think there's an easy way to do them all at once. It's not impossible, but wouldn't be very efficient and probably wouldn't actually give you the result you want, once the element is in its new position.
After the discussion, we found that the sizing issue wasn't really due to the styles, but due to the element that the width was being calculated from.
When the element is positioned normally in the page-flow, it uses its most recent positioned parent's width and then takes off margin to find the width of the child content.
When the element is removed from the page flow, its width is then independent of the parent. So to get the two to match up, record the parent's width rather than the element itself and set the width to match the parent, instead of trying to maintain the element's width.

During a transition, how do I get the resulting offsets of other elements?

Say I have two block level elements in the same container. I want to increase the height of the first (closer to top of page) one, and use a transition on that height. How do I get the resulting offsetTop of the second div, before the transition ends?
I don't want to know how to do it for this example. I want to know how to do it for a generic case. Many other elements may be going through transitions at the same time, not just the element directly above the one I am interested in.
The only way I can think of doing it is to force the transitions to end, check the position, and then restart all the transitions. ...but means I have to do it as the transitions start. ...and it causes a flicker when the page is repainted with the temporarily finished transitions.
Demo: http://jsbin.com/AyAfEXu/2
One approach would be to clone all your elements once you have added the classes that trigger the CSS transitions, then you can get the resulting properties from the cloned elements which will immediately hold the new values.
I suggest the use of a container element:
<div id="container">
<div id="one"></div>
<div id="two">
<div id="curr">current offsetTop of P:</div>
</div>
</div>
And with jQuery you would do:
$(function () {
//Kick off animations
$('#one').addClass('loaded');
//Clone container and position it outside of view
var $cloneContainer = $('#container').clone().css({
position: 'absolute',
left: '-99999px'
}).prependTo('body');
//Get offset from cloned element
var newOffset = $cloneContainer.find('#two').offset().top;
console.log(newOffset);
//Remove clone
$cloneContainer.remove();
});
Here's a working demo of this approach

How to make an element's height increase with every child added to it

I have a <div> that has children appended to it by a script. These children elements are automatically appended by a PHP script and positioned using position:absolute. I tried to give the parent <div> the style min-height:400px allowing the elements appended to the <div> to increase the parent's height. The only problem is that the height does not increase when I do this. Does anybody know what I can do to fix this?
EDIT: I am not able to use position:relative for positioning my elements. Are there any solutions that allow for position:absolute.
Yes you can use position absolute (yeee♥!)
LIVE DEMO TEST CASE
By simply doing:
$(this).height( this.scrollHeight );
or with pure JS:
this.style.height = this.scrollHeight ;
and adding this to your element's CSS:
overflow:hidden;
overflow-y:auto;
Edit:
The demo tested fine in IE10, Firefox, Chrome, Safari, and Opera.
The key point here is setting the overflow value for the x or y axis (whichever dimensions you need the size of) to auto, rather than the default value of visible. Then the scrollWidth or scrollHeight property can be used on the HTML DOM object to get the full size of the element, including any absolutely-positioned descendants.
Odd as it seems, this is entirely consistent with the fact that setting overflow:hidden for a container clips any absolutely-positioned descendants. Apparently, elements with position:absolute aren't quite as "out of the flow" as we've always been told :)
You should not use position: absolute for this because stuff that is positioned that way will be pulled out of the normal render flow. This results in the parent not noticing that its content s acually very high. Use position: relative for the child div's. This way the parent will grow automatically.

Categories