How do I place one element precisely over another element? - javascript

How do I place an input element perfectly over another element?
I am close, but not there. Please see https://output.jsbin.com/yivitupaqe/1
As seen, the input is pushed down a bit for examples 1, 2, and 3. I could fix it by getting rid of the style on the elements which had the input added to it, but don't wish to do so. For the example 4, it is way off and I think I will need to have jQuery somehow detect if the original element is a replaced or non-replaced element.
PS. Please provide explanation of what causes this behavior.
function overlayInput(e) {
var margin = e.css('margin-top') + ' ' + e.css('margin-right') + ' ' + e.css('margin-bottom') + ' ' + e.css('margin-left');
var input = $('<input/>', {
type: 'file',
name: 'bla',
style: 'position:absolute;top: 0; bottom: 0; left: 0; right: 0;cursor:pointer;z-index:9999;opacity:0;filter:alpha(opacity=0);height:' + e.outerHeight(false) + 'px;width:' + e.outerWidth(false) + 'px;padding:0;margin:' + margin //Padding shouldn't matter
});
e.wrap($('<div/>', {
style: 'position:relative; display:' + e.css('display') + ';margin:0;padding:0'
}))
.parent().append(input);
console.log(e, input[0])
}
$(function() {
var e1 = $('#e1'),
e2 = $('#e2'),
e3 = $('#e3'),
e4 = $('#e4');
overlayInput(e1);
overlayInput(e2);
overlayInput(e3);
overlayInput(e4);
});
#e1,
#e2,
#e3,
#e4 {
border: 5px solid black;
margin: 10px;
padding: 5px;
background-color: yellow;
}
#e2 {
width: 300px;
}
div {
margin-top: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>Example 1 (non-replaced inline element)<a id="e1" href="javascript:void(0)">Hello</a>bla bla bla</div>
<div>Example 2 (block element with width)
<p id="e2">Hello</p>bla bla bla</div>
<div>Example 3 (block element without width)
<p id="e3">Hello</p>bla bla bla</div>
<div>Example 4 (non-replaced inline element)
<img id="e4" alt="hi" src="http://icons.iconarchive.com/icons/hopstarter/sleek-xp-software/48/Yahoo-Messenger-icon.png" />bla bla bla</div>

I have taken a bit of time and recreated your jsbin code into jsfiddle, simplifying it and trying to illustrate my advice in the comments. It is a bit fiddly with the target elements being different types so you see slightly different effects, but for the main part, the target elements are covered with the input elements.
The key points are:
the 'original' target elements have the display and width styles that get added to the outer div that wraps everything, it also has the position: relative rule
after wrapping the original element e in the new div, get the outer dimensions of the div
the inner input can then have the standard absolute and 0 position styles along with the same width and height as the outer div
This gives us the results:
Example 1 - completely covers the link text, but not the top and bottom padding
Example 2 - completely covers the yellow box except for tiny border equivalent edge at the right hand side
Example 3 - completely covers the yellow box
Example 4 - completely covers the yellow box, but overlaps slightly by border equivalents when no image found
Hopefully this will be enough for you to work with and tweak further to get the exact levels of element coverage that you require, possibly handle different target element types to get exact coverage areas.
https://jsfiddle.net/sc7y67q0/1/

Related

Why are "fadeIn" and "fadeOut" functions moving my divs?

I'm starting a new website and I'm using JQuery for display a div inside another (a title). I have 4 divs displayed inline-block and my result need to be like this :
I'm using Jquery for display the div containing "Accueil" with the function fadeIn and fadeOut but my problem is the following : When the move is over a div, the hidden div is animated and fade in like desired but the other div (on the right) is moving down !
My html is the following :
The left box :
<div class="box-interactive box-interactive1">
<div class="contenu-box">
titi 1
</div>
<div id="bandeau1" class="bandeau">
rr
</div>
</div>
The right box :
<div class="box-interactive box-interactive2">
<div class="contenu-box">
titi 2
</div>
<div id="bandeau2" class="bandeau" style="display : block;">
accueil 2
</div>
</div>
My css :
/*CSS for boxes and interactive text*/
.box-interactive {
width: 300px;
height: 200px;
background-color: red;
margin: 20px;
display: inline-block;
size: fixed;
}
.contenu-box{
width: 300px;
height: 160px;
}
.bandeau {
width: 300px;
height: 40px;
background-image: url("../../img/Alex/accueil.png");
background-size: auto 100%;
position: relative;
display: none;
}
And my JS :
$(function(){
$("div[class^='box-interactive']").hover(
function(){
var id = new Array;
id = $(this).attr('class').split(' ');
for (var i in id) {
if(id[i].match('box-interactive.+')){
var idnum = 'bandeau'+id[i].substring(15);
$("#"+idnum+"").fadeIn(800);
}
}
},
function(){
var id = new Array;
id = $(this).attr('class').split(' ');
for (var i in id) {
if(id[i].match('box-interactive.+')){
var idnum = 'bandeau'+id[i].substring(15);
$("#"+idnum+"").fadeOut(500);
}
}
}
);
});
The second div (it works in both ways) is moving down with specificities : the top of the moving div is equal to the bottom of the first div (the one befor the hidden). Do you have an explaination ?
Fiddle : http://jsfiddle.net/Msh2T/1/ open large the right window to see the problem ;) thx
fadeIn and fadeOut will set an element to "display: none" once the animation completes, removing it from the layout. If you don't want the animation to affect the layout, animate the opacity.
$("#"+idnum+"").animate({opacity: 0}, 800);
...
$("#"+idnum+"").animate({opacity: 1}, 800);
You can float the .bandeau divs so that they aren't affecting the inline layout flow, effectively limiting their scope to the parent div.
.bandeau {
width: 300px;
height: 40px;
background-image: url("../../img/Alex/accueil.png");
background-size: auto 100%;
position: relative;
display: none;
float: left; /* ADD THIS */
}
Fiddle: http://jsfiddle.net/Msh2T/3/
One option would be to animate the opacity either to 1 or to 0 instead of using fadeIn and fadeOut:
$("#"+idnum+"").animate( { opacity:1 }, 800 );
and
$("#"+idnum+"").animate( { opacity:0 }, 500 );
Here's a working fiddle to demonstrate this approach.
A few other notes about the code...
First, your hover-in and hover-out functions are nearly identical. Any time you have that much code that is so similar, it's a very good idea to combine it into a single function.
Where you have this code:
var id = new Array;
id = $(this).attr('class').split(' ');
it's unnecessary to have the new Array, since you are just replacing the value in the next line. Also, I recommend using a plural name for an array instead of a singular name:
var ids = $(this).attr('class').split(' ');
The next line is:
for (var i in id) {
Never use a 'for..in' loop on an array. It will bite you if anyone ever augments Array.prototype with new methods or properties. Instead, use a numeric for loop or an iterator such as jQuery's $.each().
Next is this code:
if(ids[i].match('box-interactive.+')){
var idnum = 'bandeau'+id[i].substring(15);
...
When you use .match to test a string like this, you can also use it to extract the part of the string you want, without resorting to a brittle-to-maintain .substring(15) call:
var match = ids[i].match( /box-interactive(.+)/ );
if( match ) {
var idnum = 'bandeau' + match[1];
...
Now having said all this, why not simplify things much further and let jQuery do all the work for you? There's no need for any of this fancy array looping and checking of classnames. You can replace the entire JavaScript code with this:
$(function(){
$("div[class^='box-interactive']").hover(
function(){
$(this).find('.bandeau').animate( { opacity:1 }, 800 );
},
function(){
$(this).find('.bandeau').animate( { opacity:0 }, 500 );
}
);
});
Updated fiddle
(You may note that I've contradicted my first piece of advice here and didn't combine the bit of duplicate code in the hover-in and hover-out functions. But there's now so little code that that the duplication isn't worth worrying about.)
Try using z-index in your CSS to stack your divs on top of each other

How to create new div when the content is overflowing past the fixed height of the div?

CSS
.page{
width: 275px;
hight: 380px;
overflow: auto;
}
HTML
<div class="page">dynamic text</div>
How to create a new div when the dynamic text is overflowing past the fixed height of the div?
Example:
<div class="page">Some dynamic texts will appear here</div>
When the dynamic text is overflowing past the fixed height of the div, the content above will be appear like this.
<div class="page">Some dynamic</div>
<div class="page">texts will</div>
<div class="page">appear here</div>
I've tried using wordwrap function in PHP wordwrap($dynamic_text, 600, '</div><div class="page">'); it's can running, but it had a problem when the character was copied from Ms.Words.
So, by detecting the overflowing text, cut it, and then paste it into the new div element is the better solustion, i guess. But, I don't know how to do this solution using JQuery or Javascript.
Any ideas? Thanks in advance!
You can do it, and it's way more than just a couple lines of code. It took a very experienced developer a couple days. Sorry, can't share the code.
Javascript: Put the whole content into the div. You may keep it hidden or out of the DOM for a while. Traverse the div's children. Find the one whose top+scrollHeight exceeds the div's height. Traverse it recursively. Eventually, you will either find an indivisible element, e.g., an image, that doesn't fit, or a position within a text node to split the text at. Remove that part and all further elements from the div. Add them to a new one.
There are a lot of details to address, so it's not simple. But doable.
I just had something similar working today while I was searching for an answer but there doesn't seem to be anything straight forward.
Although I am using Array.reduce() you should be able to do this with Array.forEach() or any other iterating code you like.
words.reduce(function(acc, value)
This is done by calculating if the element will overflow if we add another word to it before we actually render it. The hacky thing here is to add another block element inside of it with visibility: hidden.
element.innerHTML = '<div style="visibility: hidden; height: 100%; width=100%">' + textToBeAdded + '</div>';
That way the block element still takes its parents dimensions and the parent element can be checked for overflow.
The way to check for overflow is to compare the element's scrolling height to its height:
if (element.scrollHeight > element.offsetHeight)
If it overflows we leave it as is and create a new element and put the current value (word) in the iteration. Then we attach it to the same DOM tree as the previous element (as its parent's child... like having a new brother 😜)
var newPageEl = document.createElement('div');
newPageEl.classList = 'page';
newPageEl.textContent = word;
parentElement.appendChild(newPageEl);
Hope this makes sense.
var page = document.getElementsByClassName('page')[0];
if (page.scrollHeight > page.offsetHeight) {
// is overflowing
fixOverflow(page);
}
function fixOverflow(element) {
var words = element.textContent.split(' ');
// delete previous text content
element.textContent = '';
words.reduce(function(acc, value) {
// store current element's text
var currentElementText = element.textContent.toString().trim();
var textToBeAdded = currentElementText + ' ' + value;
element.innerHTML = '<div style="visibility: hidden; height: 100%; width=100%">' + textToBeAdded + '</div>';
if (element.scrollHeight > element.offsetHeight) {
// is overflowing with the new word
element.innerHTML = "";
// leave the last page element as is
element.textContent = currentElementText;
// create another element with the new value to be added
// ** IMPORTANT replace the memory of the previous element variable
element = createPageElement(value);
// add it to the same DOM tree as the previous page element
page.parentElement.appendChild(element); // could also be document.getElementById('page-container').appendChild(element);
} else {
// if not overflowing add another word
element.innerHTML = currentElementText + ' ' + value;
}
}, "");
}
function createPageElement(text) {
// create element with class page
var newPageEl = document.createElement('div');
newPageEl.classList = 'page';
newPageEl.textContent = text;
return newPageEl;
}

Store positioning information from each element (JQuery/Javascript)

Pleasantries
I've been playing around with this idea for a couple of days but can't seem to get a good grasp of it. I feel I'm almost there, but could use some help. I'm probably going to slap myself right in the head when I get an answer.
Actual Problem
I have a series of <articles> in my <section>, they are generated with php (and TWIG). The <article> tags have an image and a paragraph within them. On the page, only the image is visible. Once the user clicks on the image, the article expands horizontally and the paragraph is revealed. The article also animates left, thus taking up the entire width of the section and leaving all other articles hidden behind it.
I have accomplished this portion of the effect without problem. The real issue is getting the article back to where it originally was. Within the article is a "Close" <button>. Once the button is clicked, the effect needs to be reversed (ie. The article returns to original size, only showing the image, and returns to its original position.)
Current Theory
I think I need to retrieve the offset().left information from each article per section, and make sure it's associated with its respective article, so that the article knows where to go once the "Close" button is clicked. I'm of course open to different interpretations.
I've been trying to use the $.each, each(), $.map, map() and toArray() functions to know avail.
Actual Code
/*CSS*/
section > article.window {
width:170px;
height:200px;
padding:0;
margin:4px 0 0 4px;
position:relative;
float:left;
overflow:hidden;
}
section > article.window:nth-child(1) {margin-left:0;}
<!--HTML-->
<article class="window">
<img alt="Title-1" />
<p><!-- I'm a paragraph filled with text --></p>
<button class="sClose">Close</button>
</article>
<article class="window">
<!-- Ditto + 2 more -->
</article>
Failed Attempt Example
function winSlide() {
var aO = $(this).parent().offset()
var aOL = aO.left
var dO = $(this).offset()
var dOL = dO.left
var dOT = dO.top
var adTravel = dOL-aOL
$(this).addClass('windowOP');
$(this).children('div').animate({left:-(adTravel-3)+'px', width:'740px'},250)
$(this).children('div').append('<button class="sClose">Close</button>');
$(this).unbind('click', winSlide);
}
$('.window').on('click', winSlide)
$('.window').on('click', 'button.sClose', function() {
var wW = $(this).parents('.window').width()
var aO = $(this).parents('section').offset()
var aOL = aO.left
var pOL = $(this).parents('.window').offset().left
var apTravel = pOL - aOL
$(this).parent('div').animate({left:'+='+apTravel+'px'},250).delay(250, function() {$(this).animate({width:wW+'px'},250); $('.window').removeClass('windowOP');})
$('.window').bind('click', winSlide)
})
Before you go scratching your head, I have to make a note that this attempt involved an extra div within the article. The idea was to have the article's overflow set to visible (.addclass('windowOP')) with the div moving around freely. This method actually did work... almost. The animation would fail after it fired off a second time. Also for some reason when closing the first article, the left margin was property was ignored.
ie.
First time a window is clicked: Performs open animation flawlessly
First time window's close button is clicked: Performs close animation flawlessly, returns original position
Second time SAME window is clicked: Animation fails, but opens to correct size
Second time window's close button is clicked (if visible): Nothing happens
Thank you for your patience. If you need anymore information, just ask.
EDIT
Added a jsfiddle after tinkering with Flambino's code.
http://jsfiddle.net/6RV88/66/
The articles that are not clicked need to remain where they are. Having problems achieving that now.
If you want to go for storing the offsets, you can use jQuery's .data method to store data "on" the elements and retrieve it later:
// Store offset before any animations
// (using .each here, but it could also be done in a click handler,
// before starting the animation)
$(".window").each(function () {
$(this).data("closedOffset", $(this).position());
});
// Retrieve the offsets later
$('.window').on('click', 'button.sClose', function() {
var originalOffset = $(this).data("originalOffset");
// ...
});
Here's a (very) simple jsfiddle example
Update: And here's a more fleshed-out one
Big thanks to Flambino
I was able to create the effect desired. You can see it here: http://jsfiddle.net/gck2Y/ or you can look below to see the code and some explanations.
Rather than having each article's offset be remembered, I used margins on the clicked article's siblings. It's not exactly pretty, but it works exceptionally well.
<!-- HTML -->
<section>
<article>Click!</article>
<article>Me Too</article>
<article>Me Three</article>
<article>I Aswell</article>
</section>
/* CSS */
section {
position: relative;
width: 404px;
border: 1px solid #000;
height: 100px;
overflow:hidden
}
article {
height:100px;
width:100px;
position: relative;
float:left;
background: green;
border-right:1px solid orange;
}
.expanded {z-index:2;}
//Javascript
var element = $("article");
element.on("click", function () {
if( !$(this).hasClass("expanded") ) {
$(this).addClass("expanded");
$(this).data("originalOffset", $(this).offset().left);
element.data("originalSize", {
width: element.width(),
height: element.height()
});
var aOffset = $(this).data("originalOffset");
var aOuterWidth = $(this).outerWidth();
if(!$(this).is('article:first-child')){
$(this).prev().css('margin-right',aOuterWidth)
} else {
$(this).next().css('margin-left',aOuterWidth)
}
$(this).css({'position':'absolute','left':aOffset});
$(this).animate({
left: 0,
width: "100%"
}, 500);
} else {
var offset = $(this).data("originalOffset");
var size = $(this).data("originalSize");
$(this).animate({
left: offset + "px",
width: size.width + "px"
}, 500, function () {
$(this).removeClass("expanded");
$(this).prev().css('margin-right','0')
$(this).next().css('margin-left','0')
element.css({'position':'relative','left':0});
});
}
});​

Draw arrow between lists

Is there any way to dynamically draw an arrow between the two highlighted list items?
So if I hovered over "Item 2" it would do this (but a straight arrow):
Item 1 Highlight 3
Item 2-----\ Highlight 1
Item 3 ----->Highlight 2
This is the code from the answer I got here a few mins ago:
Highlight item in two lists when mouseover
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
$("#qq" + n + ", #aa" + n).toggleClass("highlight");
});
jsfiddle: http://jsfiddle.net/e37Yg/1/
<ul class="list1">
<li id="qq1">sdfsdv</li>
<li id="qq2">bnvnvb</li>
<li id="qq3">nmnutymnj7</li>
<li id="qq4">cvbc</li>
<li id="qq5">45tsgd</li>
</ul>
<ul class="list2">
<li id="aa3">fgtbrtgb</li>
<li id="aa1">vbn xgbn</li>
<li id="aa5">vdgver</li>
<li id="aa4">asdasdv</li>
<li id="aa2">nvfbnfn</li>
</ul>
You don't have to use 2D drawing here. Check this out: http://jsfiddle.net/vjYuW/
I just forked and updated the fiddle you have posted above.
Here is the essential code, it handles 3 DIVs 1 pixel wide or tall to draw the lines:
HTML:
...left list...
<div id="segment1" class="hline"></div>
<div id="segment2" class="vline"></div>
<div id="segment3" class="hline"></div>
...right list...
CSS:
... formatting for ULs here, both have to be float:left...
.highlight { background-color: red; }
.hline {
display:block;
position:relative;
float:left;
height: 1px;
width: 7em;
}
.vline {
display:block;
position:relative;
float:left;
height: 1px;
width: 1px;
}
JavaScript:
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
var leftY = $('#qq' + n).position().top;
var rightY = $('#aa' + n).position().top;
var H = Math.abs(rightY-leftY);
if (H == 0) H = 1;
$('#segment1').css('top',leftY+'px');
$('#segment3').css('top',rightY+'px');
$('#segment2').css('top',Math.min(leftY,rightY)+'px');
$('#segment2').css('height',H+'px');
$("#qq" + n + ", #aa" + n + ",#segment1,#segment2,#segment3").toggleClass("highlight");
});
Note: you will probably have to tweak it a little to support all browsers - I didn't check IE6 & Co.
You can use the HTML5 canvas element to achieve this.
I'm not sure if this is the best way to do it, but I fiddled around and got this.
What I did is first I enclosed the lists in a div. The div is styled with CSS to have a relative position. This is so when you get the position with jQuery, it will give a position relative to that. Next, I put a canvas in front of the lists and disabled pointer events on it. I also added something to adjust the height of the canvas to the height of the lists. Then I added another handler for hover. When you hover over it, it will draw the arrow, and when you hover out, it'll clear the canvas.
To draw the arrow is fairly simple. First it gets the positions of the items. Then it draws a line and uses some math to orient the arrow. To get the positions is fairly easy. For the right list, you can just use the position method. For the left list, I created a temporary span and appended it to the list item, and then got the position of that.
I think for something like this you may want to use a third party drawing library such as Vector Draw Library.
You can download the library from the link and add it to your app. Then:
Include it on your page:
<script type="text/javascript" src="wz_jsgraphics.js"></script>
Then add to your hover function:
$(".list1 li, .list2 li").hover(function () {
var n = this.id.substr(2);
$("#qq" + n + ", #aa" + n).toggleClass("highlight");
//canvas is your drawing div
var jg = new jsGraphics("canvas");
jg.drawLine($("#qq" + n).offset().left + 30, $("#qq" + n).offset().top , $("#aa" + n).offset().left, $("#aa" + n).offset().top );
jg.paint();
Note that you will have to write the code to remove the line in the hover function otherwise once it is drawn it will remain. Also, I am using offset() to calculate the position of the items in the list. This should work but you may have to tweak a bit to get it to look right.
The above code works but is not complete. Maybe the second function in the hover can call clear() on the canvas. Canvas here is the enclosing div that encloses the two lists.
<script src='www.walterzorn.de/en/scripts/wz_jsgraphics.js'> </script>
function drawLine(element1, element2) {
var jg = new jsGraphics("renderGraph");
var ele1 = document.getElementById(element1);
var ele2 = document.getElementById(element2);
jg.setColor("#DDD");
jg.setStroke(5);
jg.drawLine(ele1.offsetLeft + ele1.offsetWidth/2 , ele1.offsetTop + ele1.offsetHeight/2, ele2.offsetLeft + ele2.offsetWidth/2, ele2.offsetTop + ele2.offsetHeight/2);
jg.paint();
}

Dynamically positioning elements using jQuery

I am working om a menu bar, each menu bar item is an image, when user places mouse over menu item a div with submenu will appear.
I want to place div directly under the appropriate image item (no space, and div will hover above all elements), with right side alignment, meaning the right top corner of div should be under bottom right corner of image.
Because I can't and don't want to hard code position of divs, i want to do it dynamically.
For now I have this:
$('img').each(function(){
jQuery(this).mouseenter(function(){
var menuItem = $('#' + this.id + '_menu'); //get the needed div
var imgRight = this.offset() + this.width();
});
});
The offset() method has top and left properties, you need use them, example:
var imgRight = this.offset().left + this.width();
var imgTop = this.offset().top + this.height();
After that, you will have to give the absolute positioning to the DIVs to place them below the images:
menuItem.css({
position:'absolute',
top: imgTop,
left: imgLeft,
zIndex:5000
});
So your code becomes:
$('img').each(function(){
jQuery(this).mouseenter(function(){
var menuItem = $('#' + this.id + '_menu'); //get the needed div
var imgRight = this.offset().left + this.width();
var imgTop = this.offset().top + this.height();
menuItem.css({
position:'absolute',
top: imgTop,
left: imgLeft,
zIndex:5000
});
// now show the corresponding div
menuItem.show('slow');
});
});
More Info:
http://api.jquery.com/offset/
You shouldn't have to hard code or calculate the position of these items. Any of the following CSS rules should achieve your goal: position: relative; right: 0 or float: right:.
It'd be good to see some of your markup for additional testing. www.jsfiddle.net is a great resource for this.
There are 2 ways to do this: the correct-way or the cheat way...
The correct way: you need to get the top and client height of the actuating object - client heights no prob just call it - but the top means you must get the to of all the parent objects too - use this:
function J_pos(o)
{
var x,y;
y=o.offsetTop;
x=o.offsetLeft;
o=o.offsetParent;
while(o)
{
y+=o.offsetTop;
x+=o.offsetLeft;
o=o.offsetParent;
}
return [x,y];
};
Now the top and client height you do this:
<div style=top:"+(p[0]+obj.clientHeight)+";left:"+p[1]>
The cheat-way (not so dynamic - but quick):
put a tag like a <span> around the actuating (mouseover) object. Make it position-relative. Place a <div> inside it:
<div id="ABC" style="position:absolute;left:0;display:none">
Now on mouseover put document.getElementById("ABC").style.display="" and bottom:0 — boom baby dusted. Downside to this is you have to manually do it for each instance, but if you only have 3 or so well bingo.

Categories