Found a lightway draggable function. How can I lock the draggable black block in parent area? Do i need to do a width and height to limit black block area?
online sample http://jsfiddle.net/zqYZG/
.drag {
width: 100px;
height: 100px;
background-color: #000;
}
.box{
width: 500px;
height: 400px;
background-color:red;
}
jQuery
(function($) {
$.fn.draggable = function(options) {
var $handle = this,
$draggable = this;
options = $.extend({}, {
handle: null,
cursor: 'move'
}, options);
if( options.handle ) {
$handle = $(options.handle);
}
$handle
.css('cursor', options.cursor)
.on("mousedown", function(e) {
var x = $draggable.offset().left - e.pageX,
y = $draggable.offset().top - e.pageY,a
z = $draggable.css('z-index');
$draggable.css('z-index', 100000);
$(document.documentElement)
.on('mousemove.draggable', function(e) {
$draggable.offset({
left: x + e.pageX,
top: y + e.pageY
});
})
.one('mouseup', function() {
$(this).off('mousemove.draggable');
$draggable.css('z-index', z);
});
// disable selection
e.preventDefault();
});
};
})(jQuery);
$('.drag').draggable();
Here is a simple way to do it, using the getBoundingClientRect() function: updated JSFiddle
This just constrains the l and t variables from your original code, to be within the parent node's dimensions.
See containment option!
use :
$( ".selector" ).draggable({ containment: "parent" });
Related
I am writing this after hundreds of references. I want to create/or Nest Thermostat like scrolling effect. I found this solution https://jsfiddle.net/desandro/daZmA/ but its parent has fixed position. Which I can't use within the website.
window.addEventListener( 'load', function() {
var box = document.getElementById('box'),
docHeight = document.documentElement.offsetHeight;
window.addEventListener( 'scroll', function() {
// normalize scroll position as percentage
var scrolled = window.scrollY / ( docHeight - window.innerHeight ),
transformValue = 'scale('+scrolled+')';
box.style.WebkitTransform = transformValue;
box.style.MozTransform = transformValue;
box.style.OTransform = transformValue;
box.style.transform = transformValue;
}, false);
}, false);
body {
height: 2000px;
}
#container {
width: 200px;
height: 200px;
border: 2px solid;
position: fixed;
}
#box {
width: 100%;
height: 100%;
background: red;
}
<div id="container"><div id="box"></div></div>
Can anybody assist me or suggest a good reference or a existing plugin to use?
Thanks in advance
I highly recommend the GSAP scrollTrigger plugin.
https://greensock.com/scrolltrigger/
https://greensock.com/docs/v3/Plugins/ScrollTrigger
https://codepen.io/GreenSock/pen/gOabMXv
https://greensock.com/st-demos/
It makes animating elements based on scroll position very easy. For example
gsap.timeline({
scrollTrigger: {
trigger: ".thermo",
start: "center center",
end: "bottom top",
scrub: true,
pin: true
}
})
.from(".dial", { y: innerWidth * 1.5 })
My scenario : Container (div) with some objects (div). The objects can be moved inside the container (with the option containment set to parent).
Now i need to move multiple objects at once. To do this i found this useful plugin. Unfortunately this plugin does not handle the property containment, as reported here.
My test on JSFiddle , disable this function
$(".obj").on("drag", function(ev, ui)
To active the multiple drag, click on the objects. I was able to block the drag event.
Problem of my test:
At that point i wouldn't know how to reactivate the drag.
Note
I should probably know the direction of the drag (with start - stop events). But at this point i can't stop the drag.
My solutions
But also the K Scandrett solution is very good. It is very difficult to apply in my particular case, which has been simplified in the example.
Always using this plugin for enable the multiple drag. Each time i select multiple objects and drag them, in the dragstart event i do this (change the property containment of the object, depending on the positions of the selected objects) :
//1024 * 648 is the width of the container
$(obj).unbind("dragstart").bind("dragstart" , function(ev, ui){
var dimObjFirst = {
x : parseInt($(this).css("left")),
y : parseInt($(this).css("top"))
};
if($("blablabla > div.ui-selected").length > 1){
var minLeft = 1024,maxRight = 0,minTop = 648,maxDown = 0;
$("blablabla > div.ui-selected").each(function(){
var elem = $(this);
var dim = {
w : parseInt(elem.css("width")),
h : parseInt(elem.css("height")),
l : parseInt(elem.css("left")),
t : parseInt(elem.css("top")),
};
if(dim.l < minLeft) minLeft = dim.l;
if(dim.l + dim.w > maxRight) maxRight = dim.l + dim.w;
if(dim.t < minTop) minTop = dim.t;
if(dim.t + dim.h > maxDown) maxDown = dim.t + dim.h;
});
var offsetContainer = $(".container").offset();
$(this).draggable( "option" , "containment" , [
(dimObjFirst.x - minLeft) + parseInt(offsetContainer.left),
(dimObjFirst.y - minTop) + parseInt(offsetContainer.top),
(dimObjFirst.x + (1024 - maxRight)) + parseInt(offsetContainer.left),
(dimObjFirst.y) + (648 - maxDown) + parseInt(offsetContainer.top)
]);
}
});
$(obj).unbind("dragstop").on("dragstop", function(ev, ui) {
if($("blablabla > div.ui-selected").length > 1) {
$("blablabla > div.ui-selected").each(function(){
$(this).draggable( "option" , "containment" , "parent" );
});
}
});
And add this line of code this._setContainment(); at the start of the function _mouseDrag of the jQuery UI plugin.
Looks like a fun project so....
I implemented it with a bounding box (similar to Twisty's comment).
I figured the benefit of doing it this way is that it will then constrain all multiple selected objects to the bounds of the container.
The bounding box I've coloured so you can visualise how it works. Of course you'd likely leave it transparent in practice.
Code comments are inline, but happy to answer any questions on the code if you have them.
No plugins were used (just jQuery and jQueryUI).
var disableclick = false;
var boundingBoxTop, boundingBoxBottom, boundingBoxLeft, boundingBoxRight;
var $container = $("#container");
var containerHeight = $container.height();
var containerWidth = $container.width();
var containerTop = $container.offset().top;
var containerLeft = $container.offset().left;
// add the bounding box to the container and make it draggable
var $boundingBox = $("<div id='boundingBox' style='position:absolute;background-color:#fcf5d4'>").prependTo($container);
$boundingBox.draggable({
grid: [10, 10],
containment: "parent",
stop: function( event, ui ) {
disableclick = true; // don't want to toggle selection when dragging
setTimeout(function(e){
disableclick = false;
},200);
},
});
$(".obj").click(function(e) {
if (!disableclick) {
var $objClicked = $(this);
$objClicked.toggleClass("ui-selected");
var $selectedItems = $("#container .ui-selected");
// move any items in bounding box back into container before we re-process them
$boundingBox.find('*').each(function() {
var $this = $(this);
if ($this.parent().is($boundingBox)) {
// adjust its positioning to be relative to the container
$this.css("top", ($this.offset().top - containerTop) + "px");
$this.css("left", ($this.offset().left - containerLeft) + "px");
$container.append($this); // return it to the container
}
});
// reversing co-ords to what might be expected here so that we can scale them back to what they need to be for a bounding box
boundingBoxTop = containerHeight;
boundingBoxBottom = 0;
boundingBoxLeft = containerWidth;
boundingBoxRight = 0;
// find the bounds of the smallest rectangle that will cover all the currently selected objects
$selectedItems.each(function() {
var $this = $(this);
var top = $this.offset().top - containerTop;
var bottom = $this.offset().top - containerTop + $this.height();
var left = $this.offset().left - containerLeft;
var right = $this.offset().left - containerLeft + $this.width();
boundingBoxTop = (top < boundingBoxTop) ? top : boundingBoxTop;
boundingBoxBottom = (bottom > boundingBoxBottom) ? bottom : boundingBoxBottom;
boundingBoxLeft = (left < boundingBoxLeft) ? left : boundingBoxLeft;
boundingBoxRight = (right > boundingBoxRight) ? right : boundingBoxRight;
});
// get the height and width of bounding box
var boundingBoxHeight = boundingBoxBottom -= boundingBoxTop;
var boundingBoxWidth = boundingBoxRight -= boundingBoxLeft;
if (boundingBoxBottom > 0) // will be negative when nothing is selected
{
// update the bounding box with its new position and size
$boundingBox.css("top", boundingBoxTop + "px");
$boundingBox.css("left", boundingBoxLeft + "px");
$boundingBox.css("width", boundingBoxWidth + "px");
$boundingBox.css("height", boundingBoxHeight + "px");
// add each selected item to the bounding box so we can drag the box with them in it
$selectedItems.each(function() {
var $this = $(this);
// correct the item's position to be relative to the bounding box
$this.css("top", ($this.offset().top - containerTop - boundingBoxTop) + "px");
$this.css("left", ($this.offset().left - containerLeft - boundingBoxLeft) + "px");
$boundingBox.append($this); // add item to bounding box
});
}
}
});
#container {
position: absolute;
width: 400px;
height: 150px;
background: #eee;
}
.obj {
position: absolute;
background: #ccc;
}
.ui-selected {
background: #1C90F3;
}
#obj1 {
width: 20px;
height: 20px;
left: 20px;
top: 20px;
}
#obj2 {
width: 20px;
height: 20px;
left: 100px;
top: 20px;
}
#obj3 {
width: 20px;
height: 20px;
left: 20px;
top: 100px;
}
#obj4 {
width: 20px;
height: 20px;
left: 100px;
top: 100px;
}
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/jquery-3.2.1.min.js" />
<div style="margin-bottom:10px">
Click boxes to select/deselect multiple items.<br/>Drag to move selection.
</div>
<div id="container">
<div class="obj" id="obj1"></div>
<div class="obj" id="obj2"></div>
<div class="obj" id="obj3"></div>
<div class="obj" id="obj4"></div>
</div>
I enhance the answer by using jquery ui Selectable plugin
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>jQuery UI Draggable - Default functionality</title>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<style>
#container {
position: absolute;
width: 400px;
height: 150px;
background: #eee;
}
.obj {
position: absolute;
background: #ccc;
}
.ui-selected,
.ui-selecting {
background: #1C90F3;
}
#obj1 {
width: 20px;
height: 20px;
left: 20px;
top: 20px;
}
#obj2 {
width: 20px;
height: 20px;
left: 100px;
top: 20px;
}
#obj3 {
width: 20px;
height: 20px;
left: 20px;
top: 100px;
}
#obj4 {
width: 20px;
height: 20px;
left: 100px;
top: 100px;
}
</style>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
</head>
<body>
<div id="container">
<div class="obj" id="obj1"></div>
<div class="obj" id="obj2"></div>
<div class="obj" id="obj3"></div>
<div class="obj" id="obj4"></div>
</div>
<script type="text/javascript">
var disableclick = false;
var boundingBoxTop, boundingBoxBottom, boundingBoxLeft, boundingBoxRight;
var $container = $("#container");
var containerHeight = $container.height();
var containerWidth = $container.width();
var containerTop = $container.offset().top;
var containerLeft = $container.offset().left;
// add the bounding box to the container and make it draggable
var $boundingBox = $("<div id='boundingBox' style='position:absolute;background-color:#fcf5d4'>").prependTo($container);
$boundingBox.draggable({
grid: [10, 10],
containment: "parent",
stop: function (event, ui) {
disableclick = true; // don't want to toggle selection when dragging
setTimeout(function (e) {
disableclick = false;
}, 200);
},
});
$('.obj').draggable({
grid: [10, 10],
containment: "parent",
stop: function (event, ui) {
disableclick = true; // don't want to toggle selection when dragging
setTimeout(function (e) {
disableclick = false;
}, 200);
},
});
function selectionStarted() {
$boundingBox.find('*').each(function () {
var $this = $(this);
if ($this.parent().is($boundingBox)) {
// adjust its positioning to be relative to the container
$this.css("top", ($this.offset().top - containerTop) + "px");
$this.css("left", ($this.offset().left - containerLeft) + "px");
$this.draggable("enable");
$container.append($this); // return it to the container
}
});
$boundingBox.css("top", "0px");
$boundingBox.css("left", "0px");
$boundingBox.css("width", "0px");
$boundingBox.css("height", "0px");
}
function selectedEnded() {
var $selectedItems = $("#container .ui-selected");
// reversing co-ords to what might be expected here so that we can scale them back to what they need to be for a bounding box
boundingBoxTop = containerHeight;
boundingBoxBottom = 0;
boundingBoxLeft = containerWidth;
boundingBoxRight = 0;
// find the bounds of the smallest rectangle that will cover all the currently selected objects
$selectedItems.each(function () {
var $this = $(this);
var top = $this.offset().top - containerTop;
var bottom = $this.offset().top - containerTop + $this.height();
var left = $this.offset().left - containerLeft;
var right = $this.offset().left - containerLeft + $this.width();
boundingBoxTop = (top < boundingBoxTop) ? top : boundingBoxTop;
boundingBoxBottom = (bottom > boundingBoxBottom) ? bottom : boundingBoxBottom;
boundingBoxLeft = (left < boundingBoxLeft) ? left : boundingBoxLeft;
boundingBoxRight = (right > boundingBoxRight) ? right : boundingBoxRight;
});
// get the height and width of bounding box
var boundingBoxHeight = boundingBoxBottom -= boundingBoxTop;
var boundingBoxWidth = boundingBoxRight -= boundingBoxLeft;
if (boundingBoxBottom > 0) // will be negative when nothing is selected
{
// update the bounding box with its new position and size
$boundingBox.css("top", boundingBoxTop + "px");
$boundingBox.css("left", boundingBoxLeft + "px");
$boundingBox.css("width", boundingBoxWidth + "px");
$boundingBox.css("height", boundingBoxHeight + "px");
// add each selected item to the bounding box so we can drag the box with them in it
$selectedItems.each(function () {
var $this = $(this);
// correct the item's position to be relative to the bounding box
$this.css("top", ($this.offset().top - containerTop - boundingBoxTop) + "px");
$this.css("left", ($this.offset().left - containerLeft - boundingBoxLeft) + "px");
$this.draggable("disable");
$boundingBox.append($this); // add item to bounding box
});
}
}
</script>
<script type="text/javascript">
$("#container").selectable({
start: selectionStarted,
stop: selectedEnded
});
</script>
</body>
</html>
My purpose is to split a certain timeline in divisions.
My only idea of doing it is by doing something like this:
Dividable timeline
Another thing I want to be able to do is resize them by dragging one end to the left or to the right, when this is done, the next one adjusts so they ocupy the same space.
Any division can be split further.
this is what I've come up with but I'm not pleased with it at all:
https://jsfiddle.net/syj6z05v/2/
$(function() {
$( "#resizable1" ).resizable({
containment: "#container"
});
$( "#resizable2" ).resizable({
containment: "#container"
});
$( "#resizable3" ).resizable({
containment: "#container"
});
});
$(function() {
var isDragging = false;
var okay = 0;
var next;
$(".a")
.mousedown(function() {
okay = 1;
isDragging = false;
})
.mousemove(function() {
if (okay == 1){
next = $(".a").next();
var width = 0;
$(this).parent().children().each(function() {
width += $(this).outerWidth( true );
});
next.width(next.parent().width() - (width - next.width()));
isDragging = true;
}
})
.mouseup(function() {
okay = 0;
isDragging = false;
});
});
As discussed in the comments, I think adding movable dividers would be easier, because you don't have to recalculate the position and width of every part.
I've made a little example below. It's not perfect, but I hope it gets you going. You can click to add a divider, right-click to remove one, and you can drag them by holding down the left button.
// Dragging a handle
var dragging = null;
$('.timeline').on('mousedown', '.divider', function(event){
event.stopPropagation();
if (event.which == 1)
dragging = $(this);
});
$('.timeline').on('mousemove', function(event){
if (dragging) {
var left = event.offsetX;
if ($(event.target).hasClass('divider'))
left += dragging.position().left;
dragging
.css('left', left + 'px')
.attr('data-pos', left);
}
});
$('.timeline').on('mouseup mouseenter', function(event){
if ((event.buttons && 1) !== 1)
dragging = null;
});
// Adding a handle
$('.timeline').on('mouseup', function(event){
if (! $(event.target).hasClass('timeline'))
return;
if (event.which == 1)
{
var left = event.offsetX;
var divider =
$('<div>')
.addClass('divider')
.css('left', left + 'px')
.attr('data-pos', left)
.appendTo(this);
}
});
// Removing a handle
$('.timeline').on('mouseup', '.divider', function(event){
if (event.which == 2)
$(this).remove();
});
.timeline {
position: relative;
height: 20px;
border: 1px solid blue;
}
.divider {
position: absolute;
background-color: red;
width: 1px;
height: 100%;
/* Show a cursor to indicate draggability */
cursor: ew-resize;
}
.divider::before {
/* ::Before is used to display the number */
display: block;
position: absolute;
content: attr(data-pos);
height: 100%;
/* Since it is also a capturer for the mouse events of the divider, make sure it extends a bit to the left for easy grabbing. */
padding-left: 6px;
left: -3px;
/* I added background color, so you can see the grabbable area. You would probably want to remove this in production */
background-color: rgba(255, 0, 0, 0.1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Click the time line to add markers. Drag the markers to place them on the right spot.
<div class="timeline">
</div>
I'm trying to write jQuery plugin which replace default cursor to necessary image, but I have a problem with mouseleave. It doesn't fire because cursor always before child div and never leave it, so cursor never changes back to default.
DEMO
Any ideas how to fix it?
By the way: jQuery mouseleave behavior is little strange. It fires only when you leave your div and all childe divs.
DEMO 2
My code:
// Plugin changing cursor before element
;
(function ($) {
$.fn.changeCursor = function (cursorPicUrl, dx, dy) {
function inFunction(e) {
$cursor.show();
return false;
}
function outFunction(e) {
$cursor.hide();
return false;
}
function moveFunction(e) {
var x = e.clientX-100;
var y = e.clientY-100;
$cursor.css({
"transform": "translate(" + x + "px," + y + "px)"
});
}
var $cursor = $('<div id="#custom-cursor"></div>').css({
/*cursor: none;*/
width: '150px',
height: '150px',
background: 'url("' + cursorPicUrl + '") no-repeat left top',
position: 'absolute',
display: 'none',
'z-index': '10000'
});
this.append( $cursor )
.on( "mouseenter", inFunction )
.on( "mouseleave", outFunction )
//.hover( inFunction, outFunction)
.mousemove( moveFunction );
return this;
};
})(jQuery);
$(document).ready(function () {
var url = 'http://placehold.it/150x150';
$('#test-area').changeCursor( url );
});
UPDATE
My solition here:
jquery.change-cursor
I made a couple of adjustments:
Store this in a variable up front.
Attach the cursor div to the body.
Add the top/left properties to the cursor.
DEMO
Javascript:
(function ($) {
$.fn.changeCursor = function (cursorPicUrl, dx, dy) {
var elem = this;
function inFunction(e) {
$cursor.show();
return false;
}
function outFunction(e) {
$cursor.hide();
return false;
}
function moveFunction(e) {
var x = e.clientX;
var y = e.clientY;
$cursor.css({
"transform": "translate(" + x + "px," + y + "px)"
});
}
var $cursor = $('<div id="#custom-cursor"></div>').css({
/*cursor: none;*/
width: '150px',
height: '150px',
background: 'url("' + cursorPicUrl + '") no-repeat left top',
position: 'absolute',
top: '0',
left: '0',
display: 'none'
});
$('body').append( $cursor );
elem.on( "mouseenter", inFunction )
.on( "mouseleave", outFunction )
.mousemove( moveFunction );
return this;
};
})(jQuery);
For anyone still looking something similar, try this (maybe):
$('#selector').css('cursor', 'url("/path/your_image.png"), auto');
PS: Doesn't work on primitive browsers!
This is the code I have so far for my draggable-droppable-resizable functionality:
var cont1 = $(this.el).find('.image');
var cont2 = $(this.el).find('#container2');
console.log(cont1);
$(cont1).draggable({
helper: 'clone',
cursor: 'move',
revert: 'true'
});
$(cont2).droppable({
accept: 'img',
drop: function(event, ui) {
var $canvas = $(this);
var $container = $('<div>');
if (!ui.draggable.hasClass('canvas-element')) {
var $canvasElement = ui.draggable.clone();
$canvasElement.addClass('canvas-element');
$container.draggable({
cursor: "move",
// delay: 1000,
scroll: false
// containment: "parent"
}).resizable({
//containment: "parent",
handles: 'n, e, s, ne, nw, se, sw',
aspectRatio: true
});
$container.append($canvasElement).prependTo(cont2);
$canvasElement.css({
left: $container.left,
top: $container.top,
position: 'relative',
height: '100%',
width: '100%'
});
//$canvasElement.css('left', ui.position.left-80+'px').css('top', ui.position.top-80+'px').appendTo(this);
}
}
});
Here's the jsFiddle.
What I would like to achieve is for the dropped images to remain in the place where they were dropped.
There are also some other issues - the images that have already been dropped before change their positions after a new one gets dropped and I would like to get rid of this issue.
What can I change to implement that functionality?
You will need to set the .image elements to position: absolute when inside of the container. You then need to calculate their left and top based on the position of the drop event and the offset of the container.
CSS:
#container2 .image{
position: absolute;
}
JS:
var containerOffset = $container.offset();
$canvasElement.css({
left: ui.position.left - containerOffset.left,
top: ui.position.top - containerOffset.top
});
http://jsfiddle.net/E9y8Q/7/