jQuery .offset() not retrieving correct position - javascript

I'm working on a site which dynamically creates facebook-like buttons via PHP. However, the <div> that the buttons are contained in needs to have the CSS property overflow: hidden; This is the only way I've found that works to make a two-column format which forces both columns to expand to be the length of the longest one. The only problem with this method is that any facebook-like buttons placed near the bottom of the container get clipped when someone clicks on them and they expand.
Here's the way I've tried to solve this issue:
Using jQuery, I loop through all facebook like buttons on the page and calculate their document offset using the offset() method.
I then clone() each button as well as give it absolute positioning and the calculated offset using jQuery's css() method. I hope that each cloned button will be placed in the same position of the button it was cloned from when I append it to the document.
Finally, I change each old facebook-like button's css to visibility: hidden; in order to make it invisible but still take up the space it would have previously on the page. I add the clones of the facebook-like buttons to a div without the overflow: hidden; property using the appendTo() function.
Here's my entire code for this process:
// Replaces functional facebook recommend buttons with identical ones
// in a div where they won't get clipped when they expand.
// Hides the old buttons so space is still left for new ones
$(window).load(function(){
$(".fb-recommend").each(function(){ // cycle through each recommend button
var offset = $(this).offset(); // calculate offset of each button
var newButton = $(this).clone(); // clone the button
newButton.css({'position':'absolute', 'left':offset.left, 'top':offset.top});
$(this).css("visibility","hidden"); // hide the old button
newButton.appendTo("#wrapper"); // put the new button in the wrapper where it won't get clipped
});
});
At the end of all this, I expect to have clones of each button placed where the old button was but in a different <div>. The whole process works, except that the cloned facebook-like buttons are appearing at a slightly different position than the ones they were cloned from (as PitaJ points out they seem to be off by vertical offset multiples of around 39px). You can view the issue here:
LINK TO TEST WEBSITE
As you can see, the first button is placed in the correct location (the empty space filled by its hidden clone) but the other offsets were not calculated correctly.
I'd appreciate any ideas or help offered. Let me know if you'd like me to explain better or post more code!
Edit: I figured I'd post the CSS for the facebook-like buttons here (even though all I'm changing is the margin):
#content .fb-recommend {
margin: 15px 0 5px 0;
}
Edit 2: As per UnLoco's suggestion, I added a min-height property to the fb-reccommend CSS and commented out the line of code that was hiding the old buttons so it's easier to see the problem (which is still there, though slightly lessened. The CSS now looks like this:
#content .fb-recommend {
margin: 15px 0 5px 0;
min-height: 39px;
}
Edit 3: The problem appears to have been solved in all browsers but IE by changing the CSS to this:
.fb-recommend {
min-height: 24px; // I used 24 because the fb-buttons are 24px in height
}
Final Edit? This seems to work on all browsers on my end, including IE:
.fb-recommend {
min-height: 39px;
}
I'm thinking now that the 39 might have come from the 15px margin of the old fb-button + its 24px height. I think I can get around it by simply setting the height to be 39px and not having a margin.

this is because you are retrieving the offset before the fb iframes actually load. just add a css rule like this
div.fb-recommend{min-height:39px}

I believe you're problem is some odd jQuery weirdness.
To fix this, simple change your code to this:
$(window).load(function(){
$(".fb-recommend").each(function(index){ // cycle through each recommend button
var offset = $(this).offset(); // calculate offset of each button
var newButton = $(this).clone(); // clone the button
newButton.css({'position':'absolute', 'left':offset.left, 'top':offset.top + (39*index)});
$(this).css("visibility","hidden"); // hide the old button
newButton.appendTo("#wrapper"); // put the new button in the wrapper where it won't get clipped
});
});
This will account for the weird offset problem.

Related

Fixed placed element within certain area

I have a page where I display a long list of results from a DB query.. and I also show a Google Map to the RIGHT of this long list.
Map is roughly 240px wide and maybe 600px long/height.
This MAP is inside a container DIV (#mapContainer).. that contains the map, and a dropdown box above the map canvas.
Currently, the mapContainer scrolls along with the page itself.. what I would like to do is have it be static/fixed element. So it starts/displays/is placed where I have it currently on the page.... if I scroll the page.. the map should stay fixed.. until the end (bottom) of the results are scrolled to..
(I dont want the mapContainer to scroll and cover the footer element/div)
Following this tutorial:
http://www.webgeekly.com/tutorials/jquery/a-simple-guide-to-making-a-div-static-as-you-scroll-past-it/
It doesnt stay fixed..
//sticky map placement
$(function () {
var msie6 = $.browser == 'msie' && $.browser.version < 7;
if (!msie6) {
console.log("NOT IE 6");
var top = $('#mapContainer').offset().top;
$(window).scroll(function (event) {
console.log("scrolling.......");
var y = $(this).scrollTop();
if (y >= top) {
$('#mapContainer').addClass('fixed');
console.log("class added");
}else {
$('#mapContainer').removeClass('fixed');
console.log("class removed");
}
});
}
});
The first console.log() outputs fine.. but nothign in the window.scroll() portion fires ever.
Rest of code used:
#mapContainer{
display:table;
width:240px;
float:right;
/* sticky map */
position: absolute;
top: 458px;
left: 50%;
/* width: 100px; */
margin-left: 339px;
}
#<span class="skimlinks-unlinked">mapContainer.fixed</span> {
position: fixed;
}
On the tutorials page itself.. he has a toolbar on the left side..
that stops 'being fixed' when you scroll all the way to the top.. (it will start to move with the rest of the page scroll at a certain point).. and it doesnt go all the way down to cover the footer either.
I'm not clear why the jQuery portion isnt firing.. and I'm not clear what that last style is for? (seems odd looking)
All this absolute, fixed, relative, to parent, to viewport..etc.. is confusing.
Any easy to read/follow/understand tutorials that will get me to where I want to be? Or suggestions on what I am doing wrong with the correct approach?
I looked at your Fiddle and noticed a couple things:
Your "fixed" class was not represented in the CSS. When I looked into the CSS I saw a span element wrapping a ".fixed" reference with a position property set.
You are styling the mapContainer div using the ID. This is a very rigid selector as the order of CSS selectors goes. The hierarchy of CSS selectors is specifid and IDs will override types and classes. See: http://htmlhelp.com/reference/css/structure.html
The when scrolling, I am seeing the console logging in my dev tools. Also, when inspecting the element, I am seeing it add and remove the class name.
Based on my observations, modifying the CSS selector for your container should do the trick. Adding the ID to the class will keep the CSS rule specific enough:
#mapContainer.fixed { position: fixed; }
Refer to this updated Fiddle for an example with these changes in place:
http://jsfiddle.net/pmurst8e/4/
Update: For demonstration purposes of what I was referring to with the resize I modified your example a bit. It isn't the prettiest, but it conveys the point: http://jsfiddle.net/pmurst8e/6/
Update: There are a couple issues with the latest Fiddle (v12):
The sidebar will always go fixed the moment you scroll because "top" is never calculated. It's being set to zero, and the offset calculation is commented out.
Absolute positioning is relative to the closest positioned parent. If no parent is positioned, it's relative to the window. To constrain an absolute positioned element, set the constraining parent to "position:relative;".
Instead of using a percentage and left position rule, consider positioning the sidebar to the right, relative to the "contentContainer", by a set number of pixels.
When the fixed position takes effect, we also need to set the sidebar fixed left position. Otherwise, it will use the positioning in the CSS. In contrast to absolute positioning, Fixed positioning is relative to the window, meaning an absolute element "right: 10px" will be 10px from the right of the positioned parent, but will appear 10px from the right of the window when fixed.
You don't need a float when you have absolute positioning. Absolute position removes an element from the normal flow of the document, and because of this float does not apply.
I updated the Fiddle to show how to make these adjustments. I cleaned out the float and margin from the mapContainer and left the absolute positioning. With that I set the contentContainer to relative to constrain mapContainer to it. You will also see, on the script side, I added a line to set the offset of mapContainer. Without this, when it becomes fixed it will be 10px off the right border of the window.
Updated Fiddle: http://jsfiddle.net/pmurst8e/14/
Also, you want to leave your top offset line in tact. Without that, it goes fixed the moment the scroll moves and never goes back. When that becomes the case, you're better off just setting fixed permanently.
var top = $('#mapContainer').offset().top; // you want this
Regarding the bottom boundary, you can do a couple things:
Resize the sidebar so that it shrinks to the window size. This is demoed in my example from my first post in this and the downside is it forces the sidebar to become a scrollable div so the child content is all visible.
Use a check for the bottom so that when you hit the limit, the container goes back to an absolute position, but one set at the bottom: 0 of the parent.
Something like:
var limit = $('footer').offset().top;
var $mc = $('#mapContainer');
var pos = $mc.offset().top + $mc.outerHeight();
if (pos >= limit) {
$mc.removeClass('fixed')
.addClass('bottom-set').css('left',''); // define this in CSS for bottom absoluteness
}
#mapContainer.bottomFixed {
bottom: 0;
top: auto;
}
And to be fully honest, you might save yourself some time working this all out if you take a look at the ScrollToFixed plug-in (https://github.com/bigspotteddog/ScrollToFixed). I seem to be mentioning it quite a bit lately, but this issue seems to be a popular one right now.
Incidentally, go to your OP and click the Edit button. Shrink the height of your browser and scroll down. You should see SO has a fixed sidebar that passed the footer. ;)

Preventing divs from overlapping

I need help sizing the post-title div correctly at the following link. If you scale the result panel so that it is narrower, eventually the title will overlap the date. Rather than overlapping as shown here:
I would prefer that the title wrap onto a new line to avoid this collision. optimally, I would like it to also make use of the area above the the date like so:
Currently, the title will wrap once it reaches the end of the containing post div, as shown by the blue line beneath the title.
I have included a JSFiddle for you to test with.
http://jsfiddle.net/sph74/
You can use float: right on the date to get close to what you want. No more absolute positioning:
http://jsfiddle.net/sph74/1/
This is done via float: right (and that alone on the date). The .post-title element has to be display: inline or inline-block You could also use float: left, but that makes things a lot more hectic.
You also need to properly clear after the .post-heading element which I have done the old fashioned way via overflow: hidden.

Apply gradient over page without hindering user interaction [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.

How do I get the CURRENT height of a div that changed since the page loaded?

What I Want
To determine the height of the .results div that is accurate based on its CURRENT content.
Background
As I navigate through my website, the results div is updated with dynamic content based on searches, etc. I am able to get the height of the results div, but it's only the initial value that I can get.
Code for Updating the div
$results.unbind();
$results.empty();
$results.html(newContents);
What I've Tried
Note that I print out the values so I can check it in firebug.
var $results = $('.results'); /* Prints out: */
console.log($results.height()); /* 18 */
console.log($results[0].scrollHeight); /* 18 */
console.log($results[0].clientHeight); /* 18 */
console.log($results.css('height'); /* 18px */
console.log($results.attr('height'); /* undefined */
The height extends well past the bottom of the page, so 18px is definitely not accurate. How do I get the real value?
Possible Solution?
ryanulit helped me figure out that .height() SHOULD work properly for what I'm doing. So that led to me thinking that there's some problem with events being processed properly. There are many places in the application that return false rather than calling event.preventDefault(). The problem is that when you return false, it also calls event.stopPropagation() in addition to event.preventDefault().
Update
I didn't find anywhere with return false that seemed to affect height. However, I did find out that setting the height directly in the html with style="height:0px;" updates the height properly. I'm not sure what else to try, so I'm currently stumped...
Look into the .height() function of jquery.
Here's a jsfiddle to show it is actually changing: http://jsfiddle.net/EvybH/2
Update
The height: 0px; made me think so I tested in the fiddle and updated: http://jsfiddle.net/EvybH/3/.
If you set a specific height in your css, it looks like the .height() property will always return that value. Check the fiddle. You can set min-height: 18px if you want to give the div a default height and then allow it to expand.
As both the previous posters have said, .height() should be working, however in the case of it not working I believe calling
.height(currentElement.height).height()
will return a refreshed height (although very hacky and probably discouraged).
Edit: currentElement is the element with dynamic height

jCarousel not getting drawn inside a hidden div

I am using a div to populate a ul/li list and then draw a jCarousel out of it.
So this works fine:
$('#mycarousel').jcarousel();
Here is the problem:
The div containing the ul/li items could be hidden by the click of another button. When the div is hidden, and I re-size the browser window, the jCarousel also attempts to redraw itself, but since it is hidden, it is not able to draw it properly. The result is that everything is jumbled up in the list (if I click the button again to make it visible). But again if I re-size the window now (the jumbled up jCarousel is NOT hidden now), it redraws itself correctly.
I tried getting ahold of the jCarousel instance and reload itself as soon as the button is clicked to make the div visible (the way it re-sizes itself when it is visible and window is re-sized).
To get the jCarousel, I am using:
JQuery('#mycarousel').data('jcarousel')
and it is returned as null.
How can I get the jCarousel to draw correctly?
What makes you assume that the $().jcarousel() call does anything with .data()? Better to stick with the API provided by the plugin anyway, rather than guessing at how it works under the hood. Anyway, to answer your question...
The problem is that, when a div is hidden, it has no height or width. Use the "off-left technique" rather than hiding the div, like this:
#mycarousel {
height: 100px; /* whatever height your div will have when shown */
width: 100px; /* whatever width your div will have when shown */
position: absolute:
left: -10000px;
}
When you want to show it, use $('#mycarousel').css('position', 'static') to remove the absolute positioning, and the div will jump into place.
A little more info here.
Little more debugging and found that when the browser ressizes (and the carousel is already visible), its reload function is called to adjust its position, so to help myself in the hide/show div scenario, I ended up calling the carousel api's reload function after the wrapping div becomes visible.
a bit of effort was to actually get hold of the jcarousel instance.
so it was a two step process...
get hold of the carousel instance.
var cInstance = null;
cInitCallback = function(c){
cInstance = c;
};
$('#mycarousel').jcarousel({
initCallback: cInitCallback,
});
reload the carousel on the show of the div
cInstance.reload();

Categories

Resources