What is the most efficient way to do this (jquery snippet)? - javascript

$("#content").scroll(function(e){
var distance_from_top = $("#content").scrollTop();
var theinverse = -1 * distance_from_top;
var theinverse2 = theinverse + "px";
$("#topheader").css( "marginTop", theinverse2);
});
What's the most efficient way to do the above? Basically make #topheader top margin equal to the negative distance scrolled from the top.

caching caching caching.
content = $("#content");
topheader = document.getElementById("topheader");
content.scroll(function() {
topheader.style.marginTop = -content.scrollTop() + "px";
});

Efficient or short because you could simply do this for shortness.
$("#content").scroll(function(e){
$("#topheader").css( "marginTop", -$("#content").scrollTop() + "px");
});
When probably need more context (source of the web page) if you want efficiency.

Related

Create jQuery infinite loop of items

I am trying to accomplish an infinite loop of items inside 1 main div.
The idea is to show part of the items, then slide the left one outside of the screen, whilst another is added from the right side of the screen.
The following function is working, but the animate method doesn't work, it just changing the css without animating it.
Am I doing it wrong?
Also any better approach would be welcome. I tried to search jQuery solutions but they didn't work well for me so i wanted to create another one.
jQuery(document).ready(function() {
var items = jQuery('.companies-logos div');
var temp;
var item_width = 0;
if(items.length > 9) {
items.slice(9).remove();
setInterval(function(){
jQuery('.companies-logos').append(items[9]);
items[9].style.marginLeft = '0';
item_width = items[0].offsetWidth + 12.5;
jQuery(items[0]).animate({marginLeft: '-' + item_width + 'px'}, 2000);
// items[0].style.marginLeft = '-' + item_width + 'px';
temp = items[0];
jQuery(items[0]).remove();
items.splice(0, 1);
items.push(temp);
// jQuery(items[items.length-1]).css('transition', 'all 2500ms');
}, 2500);
}
});
For those interested in achieving the wanted result from above:
$(function(){
setInterval(function(){
var item_width = $('.companies-logos ul li:first-child').width() + 25;
$(".companies-logos ul li:first-child").animate({"margin-left": -item_width}, 1500, function(){
$(this).css("margin-left",25).appendTo(".companies-logos ul");
});
}, 2000);
});

Text pagination inside a DIV with image

I want to paginate a text in some div so it will fit the allowed area
Logic is pretty simple:
1. split text into words
2. add word by word into and calculate element height
3. if we exceed the height - create next page
It works quite good
here is JS function i've used:
function paginate() {
var newPage = $('<pre class="text-page" />');
contentBox.empty().append(newPage);
var betterPageText='';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
} else {
betterPageText = betterPageText + ' ' + words[i];
}
newPage.text(betterPageText + ' ...');
if (newPage.height() >= wantedHeight) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
newPage.text(betterPageText);
newPage.clone().insertBefore(newPage)
betterPageText = '...';
isNewPage = true;
} else {
newPage.text(betterPageText);
}
}
contentBox.craftyslide({ height: wantedHeight });
}
But when i add an image it break everything. In this case text overflows 'green' area.
Working fiddle: http://jsfiddle.net/74W4N/7/
Is there a better way to paginate the text and calculate element height?
Except the fact that there are many more variables to calculate,not just only the word width & height, but also new lines,margins paddings and how each browser outputs everything.
Then by adding an image (almost impossible if the image is higher or larger as the max width or height) if it's smaller it also has margins/paddings. and it could start at the end of a line and so break up everything again.basically only on the first page you could add an image simply by calculating it's width+margin and height+margin/lineheight. but that needs alot math to get the wanted result.
Said that i tried some time ago to write a similar script but stopped cause of to many problems and different browser results.
Now reading your question i came across something that i read some time ago:
-webkit-column-count
so i made a different approach of your function that leaves out all this calculations.
don't judge the code as i wrote it just now.(i tested on chrome, other browsers need different prefixes.)
var div=document.getElementsByTagName('div')[0].firstChild,
maxWidth=300,
maxHeigth=200,
div.style.width=maxWidth+'px';
currentHeight=div.offsetHeight;
columns=Math.ceil(currentHeight/maxHeigth);
div.style['-webkit-column-count']=columns;
div.style.width=(maxWidth*columns)+'px';
div.style['-webkit-transition']='all 700ms ease';
div.style['-webkit-column-gap']='0px';
//if you change the column-gap you need to
//add padding before calculating the normal div.
//also the line height should be an integer that
// is divisible of the max height
here is an Example
http://jsfiddle.net/HNF3d/10/
adding an image smaller than the max height & width in the first page would not mess up everything.
and it looks like it's supported by all modern browsers now.(with the correct prefixes)
In my experience, trying to calculate and reposition text in HTML is almost an exercise in futility. There are too many variations among browsers, operating systems, and font issues.
My suggestion would be to take advantage of the overflow CSS property. This, combined with using em sizing for heights, should allow you to define a div block that only shows a defined number of lines (regardless of the size and type of the font). Combine this with a bit of javascript to scroll the containing div element, and you have pagination.
I've hacked together a quick proof of concept in JSFiddle, which you can see here: http://jsfiddle.net/8CMzY/1/
It's missing a previous button and a way of showing the number of pages, but these should be very simple additions.
EDIT: I originally linked to the wrong version for the JSFiddle concept
Solved by using jQuery.clone() method and performing all calculations on hidden copy of original HTML element
function paginate() {
var section = $('.section');
var cloneSection = section.clone().insertAfter(section).css({ position: 'absolute', left: -9999, width: section.width(), zIndex: -999 });
cloneSection.css({ width: section.width() });
var descBox = cloneSection.find('.holder-description').css({ height: 'auto' });
var newPage = $('<pre class="text-page" />');
contentBox.empty();
descBox.empty();
var betterPageText = '';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
var oldText = '';
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
descBox.empty();
}
betterPageText = betterPageText + ' ' + words[i];
oldText = betterPageText;
descBox.text(betterPageText + ' ...');
if (descBox.height() >= wantedHeight) {
if (i != words.length - 1) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
oldText += ' ... ';
}
newPage.text(oldText);
newPage.clone().appendTo(contentBox);
betterPageText = '... ';
isNewPage = true;
} else {
descBox.text(betterPageText);
if (i == words.length - 1) {
newPage.text(betterPageText).appendTo(contentBox);
}
}
}
if (pageNum > 0) {
contentBox.craftyslide({ height: wantedHeight });
}
cloneSection.remove();
}
live demo: http://jsfiddle.net/74W4N/19/
I actually came to an easier solution based on what #cocco has done, which also works in IE9.
For me it was important to keep the backward compatibility and the animation and so on was irrelevant so I stripped them down. You can see it here: http://jsfiddle.net/HNF3d/63/
heart of it is the fact that I dont limit height and present horizontal pagination as vertical.
var parentDiv = div = document.getElementsByTagName('div')[0];
var div = parentDiv.firstChild,
maxWidth = 300,
maxHeigth = 200,
t = function (e) {
div.style.webkitTransform = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
div.style["-ms-transform"] = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
};
div.style.width = maxWidth + 'px';
currentHeight = div.offsetHeight;
columns = Math.ceil(currentHeight / maxHeigth);
links = [];
while (columns--) {
links[columns] = '<span>' + (columns + 1) + '</span>';
}
var l = document.createElement('div');
l.innerHTML = links.join('');
l.onclick = t;
document.body.appendChild(l)

How to detect overlapping HTML elements

Is it possible to find easily elements in HTML page that are hidden by given element (div)?
I prefer jQuery if possible. Do you know such plugin or something?
I searched in jQuery API (http://api.jquery.com/), but didn't find something useful.
One possible solution is jQuery Collision extension: http://sourceforge.net/projects/jquerycollision/.
JQuery extension to return the collisions between two selectors.
Handles padding, margin, borders, and can determine either overlap or
portion outside. Returns JQuery "overlap" objects. Requires:
jquery1.8.3+, examples also require: jqueryui1.9.2+
It sounds like you're looking for something for debugging purposes, but please let me know if I've missed the question!
Firefox has a pretty neat 3D view (info here) that lets you see (more or less) exactly how the objects are being stacked. If you've never looked at it before, it's at least cool enough to check out.
You can use the following script:
http://jsfiddle.net/eyxt2tt1/2/
Basically what it does is:
calculating the dimensions of each DOM element, and comparing with user's mouse coordinate
if the match return a list of DOM elements
$(document).click(function (e) {
var hitElements = getHitElements(e);
var output = $('#output');
output.html('');
for (var i = 0; i < hitElements.length; ++i) {
output.html(output.html() + '<br />' + hitElements[i][0].tagName + ' ' + hitElements[i][0].id);
};
});
var getHitElements = function (e) {
var x = e.pageX;
var y = e.pageY;
var hitElements = [];
$(':visible').each(function () {
console.log($(this).attr("id"), $(this).outerWidth());
var offset = $(this).offset();
console.log('+++++++++++++++++++++');
console.log('pageX: ' + x);
console.log('pageY: ' + y);
console.log($(this).attr("id"), $(this).offset());
console.log('+++++++++++++++++++++');
if (offset.left < x && (offset.left + $(this).outerWidth() > x) && (offset.top < y && (offset.top + $(this).outerHeight() > y))) {
console.log('included: ', $(this).attr("id"));
console.log('from 0p far x: ', $(this).attr("id"), offset.left + $(this).outerWidth());
console.log('from 0p far y: ', $(this).attr("id"), offset.top + $(this).outerHeight());
hitElements.push($(this));
}
});
return hitElements;
}

Why does adding a class affect the reported height within the same loop using JS/jQuery

I am building a timelined comments feature for part of my app. Inspired by facebook's new timelined profiles.
As an example here is a screenshot of the current display.
As you can see the comment/reply boxes fill up all vertical space.
In order to do this I have used jQuery to do the following to the <li> elements in the ordered list of comments with nested replies.
collect the items into an object
iterate over each one and get its individual height all the while building a left and right variable with the total height of the left
hand side boxes and the total of the right hand side boxes
I then check which out of the left and right have the higher total height and apply either a timeline-left or timeline-right class to
the next comment box.
This all seems very simple however I ran into a huge problem which I am at a loss to explain.
If I apply the class within the initial loop (iteration over the elements) The height variable gets effected within the loop. It appears to be adding them together rather than replacing the current one. This of course causes the boxes to be switched to the incorrect sides and therefore fails to close the gaps. Although not relevant to the functionality I added the heights as an attribute to each element in order to better see what was going on. And without a shadow of a doubt. As soon I add the class 'timeline-left' within the same loop as the height detection, the height variable combines each loop rather than reflecting the individual elements value.
To work around this I have come up with what I consider to be a very ugly method solve the issue.
I iterate through the objects and build an array of values (height,side) for each box.
I then iterate AGAIN through this new array and apply the classes to each box.
Now this actually works fine and performance is also fine. While I usually adhere to the whole pre-optimization is bad mentality. In this case I just cannot let it lie as surely there should be no reason to iterate through the elements more than once?
Here is the current (ugly) code I am using which collects the data about each element and then re-loops a second time to apply the classes:
var side = "left",
$timeline = $("#timeline"),
$elements = $timeline.children("li").not(".timeline-spine"),
newwidth = parseInt(($("#rightcolumnwrapper").width() / 2) - 26),
length = $elements.length,
array = [],
$i = 0,
leftheight = 0,
rightheight = 0,
side = "left";
while ($i < length) {
var $this = $elements.eq($i),
height = $this.outerHeight(true);
if (leftheight > rightheight) {
side = "right";
} else {
side = "left";
}
array.push({
"side": side,
"height": height
});
if (side == "right") {
var rightheight = rightheight + height + 25;
} else if (side == "left") {
var leftheight = leftheight + height + 25;
}
$i++;
}
$.each(array, function (index, value) {
var $timelineItem = $("#timeline").children("li").not(".timeline-spine").eq(index);
$timelineItem.attr("data-height", array[index]["height"]).addClass("timeline-" + array[index]["side"]).children(".window-wrapper").width(newwidth).siblings("#pointer").addClass("pointer-" + array[index]["side"]);
//The code below is not relevant to the question but is left in for completeness
if ($timelineItem.is("#timeline-bottom")) {
$timelineItem.find("#pointer").css({
"left": (newwidth + 21) + "px"
});
} else if ($timelineItem.is("#loves-capsule")) {
if ($timelineItem.find("#love-main").height() > $this.find("#loves-wrapper").height() - 33) {
$timelineItem.height("176px").find("#loves-wrapper").css({
"height": "174px",
"margin-bottom": "-35px"
}).end().find("#love-footer").show();
}
}
});
Is there anything I can do to get this into one loop without (and I really cannot explain why) effecting the heights reported for each element?
Note: There is some other stuff going on at the end is not really relevant to the question however I have left it in for code completeness.
EDIT: Just to be clear there is no css styling other than clear:left; float:left and clear:right; float:right; in either of the css rules that I am adding. Just in case anyone thought I may be styling them to modify the height.
This is a possible "one loop" implimentation:
jQuery(document).ready(function($){
var $timeline, $elements, $wrapper, columns;
$timeline = $("#timeline");
$elements = $timeline.children("li").not(".timeline-spine");
$wrapper = $("#rightcolumnwrapper");
columns = {
left : {
height : 0,
stack : []
},
right : {
height : 0,
stack : []
}
};
$elements.each(function(index){
var $obj = $(this), col, side, obj_height;
obj_height = $obj.outerHeight(true);
if ( index === 0 || columns.left.height < columns.right.height ) {
// create a local reference to the column
col = columns.left;
side = "left";
}
else {
col = columns.right;
side = "right";
}
// Do stuff with this node
$obj.addClass('timeline-'+side); //...
// Add object height to container height
col.height = col.height + obj_height;
// push raw dom node to stack
col.stack.push(this);
});
});
However manipulating an entire "stack" of nodes may be faster than doing the same operations on every iteration of .each()
$.each(columns,function(index){
// index is left or right
var $stack = $( this.stack );
$stack.addClass('timeline-'+index)//.doMoreThings()...
});
UPDATE: After further review, I think your problem with height is this part:
.attr("data-height", height)
Try calling "data-height" something else, or use .data() instead of .attr();
I believe this (single loop) is equivalent to what you posted. Updated source, comments in source below.
var side = "left",
$timeline = $("#timeline"),
$elements = $timeline.children("li").not(".timeline-spine"),
newwidth = parseInt(($("#rightcolumnwrapper").width() / 2), 10) - 26,
length = $elements.length,
array = [],
$i = 0,
leftheight = 0,
rightheight = 0,
side = "left",
$this = {},
height = 0;
while ($i < length) {
$this = $elements.eq($i);
height = $this.outerHeight(true);
side = (leftheight > rightheight) ? "right" : "left";
// unnecessary with only one loop
/*
array.push({
"side": side,
"height": height
});
*/
// var $timelineItem = $("#timeline").children("li").not(".timeline-spine").eq(index); === var $this = $elements.eq($i)
$this.attr("data-heightY", height).addClass("timeline-" + side).children(".window-wrapper").width(newwidth).siblings("#pointer").addClass("pointer-" + side);
if ($this.is("#timeline-bottom")) {
$this.find("#pointer").css({
"left": (newwidth + 21) + "px"
});
} else if ($this.is("#loves-capsule")) {
if ($this.find("#love-main").height() > ($this.find("#loves-wrapper").height() - 33)) {
$this.height("176px").find("#loves-wrapper").css({
"height": "174px",
"margin-bottom": "-35px"
}).end().find("#love-footer").show();
}
}
if (side == "right") {
rightheight += height + 25;
} else if (side == "left") {
leftheight += height + 25;
}
$i++;
}
// now unnecessary
/*
$.each(array, function (index, value) {
var $timelineItem = $("#timeline").children("li").not(".timeline-spine").eq(index);
$timelineItem.attr("data-height", array[index]["height"]).addClass("timeline-" + array[index]["side"]).children(".window-wrapper").width(newwidth).siblings("#pointer").addClass("pointer-" + array[index]["side"]);
if ($timelineItem.is("#timeline-bottom")) {
$timelineItem.find("#pointer").css({
"left": (newwidth + 21) + "px"
});
} else if ($timelineItem.is("#loves-capsule")) {
if ($timelineItem.find("#love-main").height() > $this.find("#loves-wrapper").height() - 33) {
$timelineItem.height("176px").find("#loves-wrapper").css({
"height": "174px",
"margin-bottom": "-35px"
}).end().find("#love-footer").show();
}
}
});
*/

How to get a list of all elements that resides at the clicked point?

On user click I would like to get a list of all elements that resides at the clicked point.
For example, if user clicks on Hello here:
<div><span>Hello<span></div>
I would like to get the following list:
<span> element
<div> element
<body> element
<html> element
What would be the easiest method to get these elements ?
EDIT: Based on clarification, I think this is what you mean:
EDIT: As pointed out by #Misha, outerWidth() and outerHeight() should be used in lieu of width() and height() in order to get an accurate range.
Also, if there's nothing to prevent event bubbling on the page, then the click should be placed on the document as it will be much more efficient. Even if some other click handler prevents bubbling, you should still have the click on the document, and just handle it separately from those handler that prevent bubbling.
Example: http://jsfiddle.net/57bVR/3/
$(document).click(function(e) {
var clickX = e.pageX
,clickY = e.pageY
,list
,$list
,offset
,range
,$body = $('body').parents().andSelf();
$list = $('body *').filter(function() {
offset = $(this).offset();
range = {
x: [ offset.left,
offset.left + $(this).outerWidth() ],
y: [ offset.top,
offset.top + $(this).outerHeight() ]
};
return (clickX >= range.x[0] && clickX <= range.x[1]) && (clickY >= range.y[0] && clickY <= range.y[1])
});
$list = $list.add($body);
list = $list.map(function() {
return this.nodeName + ' ' + this.className
}).get();
alert(list);
return false;
});​
Original answer:
This will give you an Array of the tag names including the span. Couldn't quite tell if this is what you wanted.
It uses .parents() along with .andSelf() to get the elements, then uses .map() with .get() to create the Array.
Example: http://jsfiddle.net/9cFTG/
var list;
$('span').click(function() {
list = $(this).parents().andSelf().map(function() {
return this.nodeName;
}).get();
alert(list);
});​
If you just wanted the elements, not the tag names, get rid of .map() and .get().
Or if you wanted to join the Array into a String using some sort of separator, just add .join(" ") after .get(), placing your separator inside the quotes.
In the near future this should be possible:
$(document).click(function(e) {
var family = this.elementsFromPoint(e.pageX, e.pageY);
$(family).each( function () {
console.log(child);
});
});
Update 2019
Currently in editors draft:
Elements from point
use parent method to get parent of the current tag recursively or in cycle:
var parentTag = $(this).parent().get(0).tagName;
The jQuery parents() function can do this for you.
To attach a click event to all span tags, for example:
$("span").click(function() {
var parents = "";
$(this).parents().map(function () {
parents = parents + " " + this.tagName;
})
alert(parents);
});
Try something like this
$('span').click(function() {
var parents = $(this).parents();
for(var i = 0; i < parents.length; i++){
console.log($(parents[i]).get(0).tagName)
}
});
check the live demo
http://jsfiddle.net/sdA44/1/
Just javascript implementation:
window.addEventListener('click', function(e){
console.log(document.elementFromPoint(e.pageX, e.pageY))
}, false)
Worked for me in firefox and chrome as of 3-29-2017
Found this:
https://gist.github.com/osartun/4154204
Had to change elements.get(i) to elements[i] to fix it...
I have updated the demo http://jsfiddle.net/57bVR/3/, adding to the code the logic to manage also (if present):
The scrolling of the page
The zoom level html tag (in the project I work it gives to me several troubles)
$(document).click(function (e) {
var htmlZoom = window.getComputedStyle(document.getElementsByTagName('html')[0]).getPropertyValue('zoom');
var clickX = e.pageX,
clickY = e.pageY,
list,
$list,
offset,
range,
$body = $('body').parents().andSelf();
$list = $('body *').filter(function () {
offset = $(this).offset();
marginLeft = offset.left * htmlZoom - $(document).scrollLeft();
marginTop = offset.top * htmlZoom - $(document).scrollTop();
outerWidth = $(this).outerWidth() * htmlZoom;
outerHeight = $(this).outerHeight() * htmlZoom;
range = {
x : [marginLeft,
marginLeft + outerWidth],
y : [marginTop,
marginTop + outerHeight]
};
return (clickX >= range.x[0] && clickX <= range.x[1]) && (clickY >= range.y[0] && clickY <= range.y[1])
});
$list = $list.add($body);
list = $list.map(function () {
return this.nodeName + ' ' + this.className
}).get();
alert(list);
return false;
});

Categories