If I have markup like this:
<!-- Other Content -->
<div id="container">
<div class="draggable">
</div>
<div class="draggable">
</div>
</div>
<!-- Other Content -->
How can I have the container div expand vertically (I only want #container to be capable of expanding downward) as I drag one of the draggables? I've thought about using the drag callback, but there's gotta be a CSS way, right?
You can't do it with css, because div#container element didn't get any class triggered from draggables.
Best way is to do it with callback:
$('.draggable').draggable({
start: function(event, ui) { $('#container').addClass('expand'); },
stop: function(event, ui) { $('#container').removeClass('expand'); }
});
I have made some progress, but this is still not working the way I would like it to.
Here's how I now have it partially working (using the markup above):
var dragCatch = function(event, ui) {
var containerOffset = $('#container').offset().top;
var containerBottom = containerOffset + $('#container').height();
var componentHeight = $(this).height();
if (event.pageY > containerBottom - componentHeight - 2) {
$('#container').height(event.pageY - containerOffset + componentHeight + 2);
$(this).css('top', event.pageY - containerOffset);
}
};
$(this).each(function() {
$(this).draggable({
grid: [117,1]
,snap: '.draggable'
,snapMode: 'outer'
,containment: '#container'
,drag: dragCatch
})
});
This code will successfully resize the #container div, but will stop moving the .draggable div where #container used to end. If I let go, then drag again, then the .draggable will again move to where #container ends, then will stop even if #container continues to expand in size.
The conclusion that I have come to is that this may not even be possible. I went with an alternate method of using jQuery.resizable to do it manually instead.
Related
Even though console log give the while expression as false the loop runs infinetly.
var tile_height;
$(document).ready(function(){
tile_height = $("#department > .front").height();
});
function tile_animate(e){
console.log(($('> .front',e).css("height") > '0px') && ($('> .back',e).height() < tile_height));
while(($('> .front',e).css("height") > '0px') && ($('> .back',e).height() < tile_height))
{
$('> .front',e).animate({height: '-=1px'});
$('> .back',e).animate({height: '+=1px'});
}
}
I have tried it using if statement as a recursive function but that too does not work. Using while causes browser to stop responding. The html code that calls the function is
HTML code
<div class="tiles" id="gallery" onmouseover="javascript:tile_animate(this)">
<div class="front">
</div>
<div class="back">
</div>
</div>
The issue is that you are using a loop while hovering. Better, is that you use the jQuery animate in a style and apply it when necessary.
However, the way you have described the problem, it looks like you will shrink the div once - there is nothing to restore it afterwards.
I noticed that the div was named 'department' but the JavaScript was looking for 'gallery' - so I changed that...
So, to perfrom this as you have your code now, a few tweaks are in order:
Remove the while loop and perform a check instead while hovering.
The check is to see if the animation is already occurring.
If the animation is occurring, ignore the hover.
If the animation is not already occurring, set a flag to say it is, and perform the required animation - not reduce the size of the div pixel by pixel (this is what the animation does for you).
When done, the div will have a height of 0px (and cannot be hovered over again).
If you wanted to restore the div later, then store its original dimensions prior to the animation, and restore them when needed (not forgetting to reset the inLoop variable)...
var tile_height;
var inLoop = false;
$(document).ready(function() {
tile_height = $("#gallery > .front").height();
});
function tile_animate(e) {
if (!inLoop && ($('> .front', e).css("height") > '0px') && ($('> .back', e).height() < tile_height)) {
inLoop = true;
$('> .front', e).animate({
height: '0px'
});
$('> .back', e).animate({
height: tile_height
});
}
}
Javascript is single threaded. In particular your code has to finish execution before another thread can start. The other thread you are really interested in is Jquery's animation loop which by default loops on a 14 ms timer thread.
In consequence your while loop is running infinitely because animation code never runs to decrement the height you are testing and expect to reach zero.
Since you are using jQuery why don't you just embed your code and trigger the function from an event handler in JS:
var tile_height;
$(document).ready(function() {
tile_height = $("#department > .front").height();
$("#gallery").mouseover(function() {
e = this;
console.log(($('> .front', e).css("height") > '0px') && ($('> .back', e).height() < tile_height));
while (($('> .front', e).css("height") > '0px') && ($('> .back', e).height() < tile_height)) {
$('> .front', e).animate({
height: '-=1px'
});
$('> .back', e).animate({
height: '+=1px'
});
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="tiles" id="gallery">
<div class="front">front</div>
<div class="back">back</div>
</div>
I'm working on my first JQuery project, and i've hit a bit of a roadblock. I'm trying to allow drag & drop reordering of a set of nested lists (ul). Everything is working with the exception of the positioning. The goal is to vertically center the draggable relative to the cursor (horizontal movement is restricted), so that an li with elements nested inside it can be easily dropped. Here's the relevant JS:
$(function() {
$( ".organizerlink" ).draggable({ axis: "y",
containment:"#organizer",
scroll: false ,
helper: "original",
revert: "invalid",
cursorAt: { top: Math.round($(this).outerHeight() / 2)}
});
and HTML:
<ul id="organizer">
<li class="organizerTarget"> </li>
<li class="organizerlink" id="dbid-1">Page
<ul><li class="organizerTarget organizerNewParent"> </li></ul>
</li>
<li class="organizerTarget"> </li>
<li class="organizerlink" id="dbid-2">About
<ul>
<li class='organizerTarget'> </li>
<li class='organizerlink' id="dbid-3">Another Page<ul><li class="organizerTarget organizerNewParent"> </li></ul></li>
<li class='organizerTarget'> </li>
<li class='organizerlink' id="dbid-4">Example<ul><li class="organizerTarget organizerNewParent"> </li></ul></li>
</ul>
</li>
<li class="organizerTarget"> </li>
<li class="organizerlink" id="dbid-27">Stuff
<ul><li class="organizerTarget organizerNewParent"> </li></ul>
</li>
Some stuff I've already tried:
setting cursorAt to $(this).height() - didn't work, i'm guessing height() pulls the css heights in, but they aren't defined explicitly so it jumps to 0
setting it to outerHeight() gives me an error "elem.style is undefined" in firebug
I know the outerHeight element exists in jquery, and based on the API doc it would appear that it can be calculated automatically, even if the CSS is undefined, so I'm thinking that is the right way to be going, and perhaps $(this) is just the wrong spot to be looking for it.
I also wanted to center my draggable objects after I pick them up. My solution:
$(".dragme").draggable({
start: function(event, ui) {
$(this).draggable("option", "cursorAt", {
left: Math.floor(this.clientWidth / 2),
top: Math.floor(this.clientHeight / 2)
});
}
});
Worked around the initial problem, see the comment to my initial post.
EDIT:
Tolerance option "pointer": The mouse pointer overlaps the other item.
$(".sortable").sortable({
containment: "parent",
tolerance: "pointer"
});
This works fine if draggable have helper object. Just put it in start method of draggable as below code.
start: function(event, ui) {
$(this).draggable("option", "cursorAt", {
left: Math.floor(ui.helper.width() / 2),
top: Math.floor(ui.helper.height() / 2)
});
}
I just spent a couple of hours smashing my head against the wall to try to reliably make this work, so I thought I'd post the solution :)
Issue:
Draggable cannot set cursorAt for 'clone' drags during instantiation for dynamic values (i.e. the center of a draggable proxy of variable size) because ui.helper hasn't been created yet! You can set it inside the start handler but it will not work for the first drag.
Solution:
Set the cursorAt inside start, but ALSO manually override the ui.position for the drag event, which occurs continuously. Using a firstRun boolean, you can prevent it from running needlessly. You must set the default cursorAt to the top left for repositioning.
Enjoy!
$jobs.each( function( index, element ) {
var $el = $(element),
firstRun = true;
/*
* jQuery UI Draggable
* See #link: http://api.jqueryui.com/draggable/
*
* Note that for clone drags, the first drag cannot center the proxy because ui.helper hasn't been created yet
* so need to set it manually for first drag. Subsequent drags can use the cursorAt property.
* See my #link:
*/
$el.draggable({
cursorAt : [0, 0], // Set origin then update dynamically
drag : function( evt, ui ) { // Fires after start event, and continuously during drag
if ( firstRun ) {
// Reset proxy position here since cursorAt cannot be set for
ui.position.left -= Math.floor( ui.helper.width()/ 2 );
ui.position.top -= Math.floor( ui.helper.height()/ 2 );
};
},
helper : 'clone',
start : function( evt, ui ) { // Fires once
if ( firstRun ) {
// Center proxy relative to cursor for future drags
$(this).draggable( 'option', 'cursorAt', {
left : Math.floor( ui.helper.width()/ 2 ),
top : Math.floor( ui.helper.height()/ 2 )
});
};
// Set cursor ( 'cursor' option just applies style to <body> )
ui.helper.css( 'cursor', 'move' );
},
stop : function( evt, ui ) { // Fires once
if ( firstRun ) {
firstRun = false;
};
}
});
In case of multiply draggable objects Goz's answer didn't work for me, so here is my solution without using cursorAt option
var offsetX = null;
var offsetY = null;
$(".item").draggable({
helper:"clone",
start:function(e,ui){
offsetX=null;
offsetY=null;
},
drag:function(e,ui){
if(offsetX===null){
offsetX = e.clientX-ui.offset.left;
offsetY = e.clientY-ui.offset.top;
}
ui.position.left += offsetX - Math.floor( ui.helper.outerWidth()/ 2 );
ui.position.top += offsetY - Math.floor( ui.helper.outerHeight()/ 2 );
}
});
.desk{
width:300px;
height:300px;
border:2px dashed gray;
}
.item {
padding:8px;
margin:2px;
border:1px solid blue;
background: rgba(0,0,80,.3);
display:inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div class="desk">
<div class="item">Item 1</div>
<div class="item" style="width:100px;">Item 2</div>
<div class="item" style="height:50px;">Item 3</div>
</div>
#yuri gor:
Your answer worked for me, but you could have just inited the offset from start:
var offsetX = null;
var offsetY = null;
$(".item").draggable({
helper:"clone",
start:function(e,ui){
offsetX = e.clientX-ui.offset.left;
offsetY = e.clientY-ui.offset.top;
},
drag:function(e,ui){
ui.position.left += offsetX - Math.floor( ui.helper.outerWidth()/ 2 );
ui.position.top += offsetY - Math.floor( ui.helper.outerHeight()/ 2 );
}
});
recently for a website I am working on I wanted to create a horizontal divider capable of resizing two elements on a page using jquery.
Basically:
Content
___Divider_____
Content
When: the divider is dragged, it should resize the "Content" elements either side of it to the users desired size.
Here is what i have so far.
<div id="WorkRequests"></div>
<div id="Divider" style="height:10px; padding:5px; cursor:n-resize;"><hr /></div>
<div id="WorkRequest_Ajax"></div>
And the script:
var totalHeight = $("#Divider").parent().height();
function ResizePage(divPosition) {
var validDrag = true;
// Math
var minPercent = totalHeight * 0.25;
var minBuffer = totalHeight * 0.05;
var topHeight = divPosition.top - $("#content").position().top;
var bottomHeight = (totalHeight - divPosition.top);
// Check Drag
if (topHeight < minPercent) {
validDrag = false;
$("#WorkRequests").height(minPercent + minBuffer);
}
if (bottomHeight < minPercent) {
validDrag = false;
$("#WorkRequest_Ajax").height(minPercent + minBuffer);
}
// Set Heights
if (validDrag) {
$("#WorkRequests").height(topHeight);
$("#WorkRequest_Ajax").height(bottomHeight);
}
return validDrag;
}
$("#Divider").draggable({ axis: "y", drag: function (event, ui) { return ResizePage($(this).position()); } });
However when I drag the divider it simply jumps around and locks at either extremity, I have tried many different calculations etc, but I am afraid I just simply do not understand the factors in resizing both elements.
So does anyone know a jquery plugin that will do this for me, or how i can fix my attempt?
Thanks,
Alex.
You may also checkout the UI.Layout jQuery plugin. Here's a demo.
You should just use the jquery resizable interaction : http://jqueryui.com/demos/resizable/
It's easy enough to restrict the dragging areas so you can only resize horizontally (but I think what you actually need is a vertically resizable area)
JQuery UI's .resizable function does not support position: fixed; elements. The moment you try to resize them it switches their position attribute to absolute. Any recommended fixes?
I have some chat windows that pop up and are draggable around the document. They are position fixed so that they don't scroll with the page behind them. They all work perfectly until you try to resize a window, that's when it transitions to position: absolute; and then gets left behind when the page scrolls.
I tried handling the resize stop event and changing the position to fixed:
stop: function (event, ui)
{
$(chatWindow).css('position', 'fixed');
}
This doesn't work because the positioning (top: and left:) are not correct for the fixed element and when you stop resizing the element switches to fixed positioning and jumps to weird places on the page. Sometimes jumps out of the page boundries and is lost forever.
Any suggestions?
To get over this problem I wrapped the .resizable() block with the .draggable() block:
<div id="draggable-dealie">
<div id="resizable-dealie">
</div>
</div>
the corresponding js:
$("#draggable-dealie").draggable();
$("#resizable-dealie").resizable();
and ensure you have the property position:fixed !important; set in the #draggable-dealie CSS:
#draggable-dealie {
position:fixed !important;
width:auto;
height:auto;
}
I have a demonstration at: http://jsfiddle.net/smokinjoe/FfRRW/8/
If, like me, you have a fixed div at the bottom of the page, then you can just add !important to these css rules to make it stick at the bottom :
.fixed {
position: fixed !important;
top: auto !important;
bottom: 0 !important;
left: 0;
right: 0;
}
And just make it resizable by its north border :
$('.fixed').resizable({
handles: "n"
});
Simply force position:fixed on the element when resizing starts and when resizing stops.
$(".e").draggable().resizable({
start: function(event, ui) {
$(".e").css("position", "fixed");
},
stop: function(event, ui) {
$(".e").css("position", "fixed");
}
});
JSFiddle: http://jsfiddle.net/5feKU/
No beauty but how about saving the position (top and left) in separate vars on the start of the resize (I think the method is called "start")?
UPDATE (Thank you for the comment... The event fires too late):
As JqueryUI generates a second object "ui" on making a object resizable it gives that ui-object a field: ui.originalPosition... That should be the position of the fixed element before the resizing...
Here's the solution I came up with, it's a little more code than I'd like, but it fixes the problem:
$("#test").resizable({stop:function(e, ui) {
var pane = $(e.target);
var left = pane.attr("startLeft");
var top = pane.attr("startTop");
pane.css("position", "fixed");
pane.css("top", top);
pane.css("left", left);
}});
$("#test").draggable({create: function(e, ui) {
var pane = $(e.target);
var pos = pane.position();
pane.attr("startLeft", pos.left + "px");
pane.attr("startTop", pos.top + "px");
}, stop: function(e, ui) {
var pane = $(e.target);
pane.attr("startLeft", ui.position.left + "px");
pane.attr("startTop", ui.position.top + "px");
}});
This stores the top and left position in the html element (needs xhtml doctype to be valid) and uses that information to set the position at the end of the resizing event.
This is a dirty solution but works for me.
this.resizable({
start:function (event, ui){
x =ui.originalPosition.left+ $(ui.originalElement).parent().scrollLeft()
y = ui.originalPosition.top+ $(ui.originalElement).parent().scrollTop()
},
resize:function (event, ui){
$(ui.originalElement).css('left' , x);
$(ui.originalElement).css('top' , y);
}
I had that problem today, and I absolutely hate "un-aesthetic" workarounds... So I decided to experiment a little to see if there wasn't a "better" solution ...and at some point I had a hunch:
html:
<div class="fixed"></div>
javascript (jquery):
$('.fixed').resizable();
$('.fixed').draggable();
css:
.fixed{
position: fixed !important;
}
Jquery just got outplayed by CSS, damn!
This question already has answers here:
Animate element to auto height with jQuery
(21 answers)
Closed 7 years ago.
I'm trying to get an element to animate to its "natural" height - i.e. the height it would be if it had height: auto;.
I've come up with this:
var currentHeight = $this.height();
$this.css('height', 'auto');
var height = $this.height();
$this.css('height', currentHeight + 'px');
$this.animate({'height': height});
Is there a better way to do this? It feels like a bit of a hack.
Edit:
Here's a complete script to play with for anyone that wants to test.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<title>jQuery</title>
<style type="text/css">
p { overflow: hidden; background-color: red; border: 1px solid black; }
.closed { height: 1px; }
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js"></script>
<script type="text/javascript">
$().ready(function()
{
$('div').click(function()
{
$('p').each(function()
{
var $this = $(this);
if ($this.hasClass('closed'))
{
var currentHeight = $this.height();
$this.css('height', 'auto');
var height = $this.height();
$this.css('height', currentHeight + 'px');
$this.animate({'height': height});
}
else
{
$this.animate({'height': 1});
}
$this.toggleClass('closed');
});
});
});
</script>
</head>
<body>
<div>Click Me</div>
<p>Hello - I started open</p>
<p class="closed">Hello - I started closed</p>
</body>
</html>
I permit myself to answer this thread, even if it's been answered a long time ago, cuz it just helped me.
In fact, i don't understand the first answer : why opening a half-closed element to get its height, and then closing it again ?
At the beginning, you hide the element so that just a part of it appears, right ? The best way (i believe) to do this is onready, with javascript. So, when you hide the element onready, just save the orig height in a var, so you don't have to hide(onready)-show-save height-hide to be able to toggle the elements visibility.
Look at what i did, it works perfectly :
$(document).ready(function(){
var origHeight = $("#foo").css('height');
$("#foo").css({"height" : "80px"});
$("#foo .toggle").bind("click", function(event){ toggleFoo(event, lastSearchesMidHeight); });
});
Here, when you call your toggle function, you know what is your original element height without wanking around.
I wrote it fast, hoping it could help someone in the future.
the easiest solution I found was to simply wrap the content element with a div that is limited in height and set to overflow:hidden. This truncates the inner content element to the height of the wrapping div. when the user clicks, hovers, etc. to show the full height of the content element - simply animate the wrapping div to the height of the inner content div.
I could suggest an equally-hackish solution...Clone the element, position it out of view, and get its height...then delete it and animate your original.
That aside, you could also use $.slideUp() and $.slideDown():
$this.hasClass('closed') ? $(this).slideDown() : $(this).slideUp() ;
If you need to keep a 1px line, you can apply that with a parent element:
<div style='border-top:1px solid #333333'>
<div class='toggleMe'>Hello World</div>
</div>
And apply the slidingUp/Down on the .toggleMe div.
I'd also like to chime in on this old thread, if I may, in case my solution helps anyone. My specific situation is this: I have some div's that are set with a max-height value that limits them to three lines tall, and when the user mouseovers them I want them to expand to their natural height; and when the mouse cursor leaves the div, I want them to shrink back down to the clipped, max-three-lines-tall height. I need to use the CSS max-height property, rather than height, because I have some div's that contain only one or two lines of text and I don't want them unnecessarily tall.
I tried many of the solutions in this thread, and the one that worked for me was the 'hackish suggestion' involving cloned elements suggested by Jonathan Sampson. I translated his idea into the following code. Please feel free to suggest improvements.
The functions are delegated to a parent element to handle div's created via an Ajax call. The div.overflow_expandable class has the following declaration: { max-height: 5em; overflow: hidden; }
$('#results').delegate('div.overflow_expandable', 'mouseenter', function() {
var $this = $(this);
// Close any other open divs
$('#results div.overflow_expandable').not($(this)).trigger('mouseleave');
// We need to convert the div's current natural height (which is less than
// or equal to its CSS max-height) to be a defined CSS 'height' property,
// which can then animate; and we unset max-height so that it doesn't
// prevent the div from growing taller.
if (!$this.data('originalHeight')) {
$this.data('originalHeight', $this.height());
$this.data('originalMaxHeight', parseInt($this.css('max-height')));
$this.css({ 'max-height':'none',
height: $this.data('originalHeight') });
}
// Now slide out if the div is at its original height
// (i.e. in 'closed' state) and if its original height was equal to
// its original 'max-height' (so when closed, it had overflow clipped)
if ($this.height() == $this.data('originalHeight') &&
$this.data('originalMaxHeight') == $this.data('originalHeight')) {
// To figure out the new height, clone the original element and set
// its height to auto, then measure the cloned element's new height;
// then animate our div to that height
var $clone = $this.clone().css({ height: 'auto', position: 'absolute',
zIndex: '-9999', left: '-9999px', width: $this.width() })
.appendTo($this);
$this.animate({ height: $clone.height() }, 'slow');
$clone.detach();
}
}).delegate('div.overflow_expandable', 'mouseleave', function() {
var $this = $(this);
// If the div has 'originalHeight' defined (it's been opened before) and
// if it's current height is greater than 'originalHeight' (it's open
// now), slide it back to its original height
if ($this.data('originalHeight') &&
$this.height() > $this.data('originalHeight'))
$this.animate({ height: $this.data('originalHeight') }, 'slow');
});
Found this post and end up using Greg's original 1px suggestion - works great!
Just added a callback to the animate function, to set the height of the element to 'auto' when the animation ends (in my case, the content of that specific element could change and be bigger).
$('div').click(function() {
if($('p').is(':hidden')) {
$('p').slideUp();
} else {
$('p').slideDown(function() { $('p').css('height','1px'); });
}
}
That should set the height of the p tags to be 1px once they've finished sliding.
This worked for me.
<div class="product-category">
<div class="category-name">
Cars
</div>
<div class="category-products" style="display: none; overflow: hidden;">
<div class="product">Red Car</div>
<div class="product">Green Car</div>
<div class="product">Yellow Car</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
$('.product-category .category-name').click(function() {
if ($(this).parent().hasClass('active')) {
$(this).parent().removeClass('active');
var height = $(this).parent().find('.category-products').height();
$(this).parent().find('.category-products').animate({ height : '0px' }, 600, function() {
$(this).parent().find('.category-products').height(height).hide();
});
} else {
$(this).parent().addClass('active');
var height = $(this).parent().find('.category-products').height();
$(this).parent().find('.category-products').height(0).show().animate({ height : height + 'px' }, 600);
}
});
});
</script>
My solution is to store in the data attribute of the close button the original size of container (could have been stored also in the container itself, if you don't use the same button to also show again the container):
$(document).ready(function(){
$('.infoBox .closeBtn').toggle(hideBox, showBox);
});
function hideBox()
{
var parent = $(this).parent();
$(this).text('Show').data('originalHeight', parent.css('height'));
parent.animate({'height': 20});
return false;
}
function showBox()
{
var parent = $(this).parent();
$(this).text('Hide');
parent.animate({
'height': $(this).data('originalHeight')
});
return false;
}
I wanted to point to this answer, which suggest setting the height to "show" with the animate() function. I had to edit my "slideUp" style animate to use height:"hide" to work with it.