Draw arrow between lists - javascript

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();
}

Related

How do I place one element precisely over another element?

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/

How to vertically align a list on one item, with HTML/CSS/JavaScript?

I have an HTML document with a very long list, like this:
<ol>
<li>frog</li>
<li>fish</li>
<li>flamingo</li>
<li>ferret</li>
<li><div class="marked">fox</div></li>
...
</ol>
The list is too long for all items to be visible on the screen. I've marked one item with div class="marked"> and </div>. Is there some way to vertically center the list on that marked item.
If the list is too long, it should run off the edge of the visible screen, with no scrollbars created.
Here is an example of how it appears in the browser window:
__________________
| 2.fish |
| 3.flamingo |
| 4.ferret | <-- This marked item is centered.
| 5.elephant |
|___6.mouse________| <-- No scrollbars.
How can I vertically align a list on a marketed item?
Here's a solution based on James G.'s code, which avoids the loop by using getBoundingClientRect():
http://jsfiddle.net/d8d5jjvc/4/
var list = document.getElementById('list'),
marked= document.getElementById('marked'),
crm= marked.getBoundingClientRect(),
height= crm.bottom-crm.top;
list.scrollTop= (crm.top-height)-(list.clientHeight-height)/2;
Ok, I really hope I understood you correctly, because this took WAY longer than I expected.
Working JSfiddle
First you have to find the element you're centering on, the list, the height of the items in the list, and the index of the element you're centering on. (I stole a function from here to do that last bit, as well as changing your class to an id. You can figure that out if it needs to be a class)
function arrayObjectIndexOf(myArray, searchTerm) {
for(var i = 0, len = myArray.length; i < len; i++) {
if (myArray[i].firstChild === searchTerm) return i;
}
return -1;
}
var list = document.getElementById('list'),
listItems = list.children,
listItemHeight = list.scrollHeight / listItems.length,
target = elem,
index = arrayObjectIndexOf(listItems, target);
Then the tricky part was the math. First we get the height of elements above the element we're centering on, then we subtract the height of the container, minus the item we're centering on, over 2. If that makes any sense. You might want to draw it if you don't understand the math, it'll make more sense on paper.
list.scrollTop = (index * listItemHeight) - ((list.clientHeight - listItemHeight) / 2);
You display a list vertically with the display option.
display: inline;
I usually use the inline-block parameter, this is
display: inline-block;
Then, you've to do this on your css.
.marked: { display: inline-block; }
I hope this works for you.

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});
});
}
});​

How to cycle through each li or div front and backwards using jquery

I have a ul with around five <li> items. E.g.
<ul>
<li>Step 1 : Take food</li>
<li>Step 2 : Go Around</li>
<li>Step 3 : Deliver</li>
</ul>
Also I have links like
Previous
and
Next
I have to show the first li at first. Then when the next link is clicked, it should now show 2nd <li> and so on. Same for previous link. Please help.
following is the complete code:
$(document).ready(function()
{
var ul = $('ul');
// hide all li
ul.find('li').hide();
// make first li as current
ul.find('li').first().addClass('current').show();
// setup previous click handler
$('a#prev').click(function()
{
var prev = ul.find('li.current').prev();
if( prev.length )
{
ul.find('li.current').removeClass('current').hide();
prev.addClass('current').show();
}
});
// setup next click handler
$('a#next').click(function()
{
var next = ul.find('li.current').next();
if( next.length )
{
ul.find('li.current').removeClass('current').hide();
next.addClass('current').show();
}
});
});
have a look at the aptly named jQuery Cycle plugin.
http://www.malsup.com/jquery/cycle/scrollhv.html
If you are only showing one element, all you need to do is use the DOM tree as a search. If you want the next element, find the element that is currently being shown, hide it, and show its next sibling. If you are doing previous, then hide the current item and select the previous sibling.
If you are unsure of how to do this, just Google around for DOM navigation. It isn't too bad.
If at all possible, I would simply use some naming convention for your LI (in the id attribute) that you could very quickly select using jQuery. For instance, if your shown element is going to have a class that the rest won't have, you can select that element quickly using jQuery, grab its id, and modify it in some way to select the previous or next element.
as boerema said something along these lines (its untested!)
put a class "selected" on a li that starts as being shown
<ul>
<li>Step 1 : Take food</li>
<li class="selected">Step 2 : Go Around</li>
<li>Step 3 : Deliver</li>
</ul>
$("#prev").click(function(){
$(".selected").hide().removeClass("selected").prev().show().addClass("Selected");
});
$("#next").click(function(){
$(".selected").hide().removeClass("selected").next().show().addClass("Selected");
});
here is a quick demo : http://jsbin.com/oduli4
var width = 500;
var height = 250;
var slide = 0;
var speed = 500;
var size = 0;
$(document).ready(function() {
size = $('#slider').find('li').length;
$('#slider').find('ul').width(width * size).height(height);
$('#slider li, #slider img').width(width).height(height);
$('#next').bind('click',function() {
if(slide > img_width * (size - 1) *(-1)) {
slide -= width;
slider(slide);
}
});
$('#prev').bind('click',function() {
if(slide < 0) {
slide += width;
slider(slide);
}
});
});
function slider(slideMargin) {
$('#slider ul').stop().animate({'left':slideMargin}, speed );
}

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