THE PROBLEM
I'm having a minor problem dragging elements onto a scalable div container.
Once the element is actually in the container, the elements drag fine and work the way they are supposed to.
Larger elements that are dragged onto the scalable container don't have too much of an issue.
But when smaller elements are dragged, you can see that the mouse is no longer attached to said element and when it is dropped, it drops a little off where it is supposed to drop.
I'm trying to find a solution that my mouse stays on the element and it drops where it is supposed to drop.
I've solved problems bit by bit and you can see below but this is the last piece of the puzzle that's driving me mad. If anyone has the time to lend a hand, it would be greatly appreciated.
Here is a codepen - click and drag the two blue elements onto the white container to try it out
Codepen
Full Screen View
Short GIF in Action
This wil help making sure that the droppable area works with a scaled container.
$.ui.ddmanager.prepareOffsets = function(t, event) {
var i, j, m = $.ui.ddmanager.droppables[t.options.scope] || [],
type = event ? event.type : null,
list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
droppablesLoop: for (i = 0; i < m.length; i++) {
if (m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0], (t.currentItem || t.element)))) {
continue;
}
for (j = 0; j < list.length; j++) {
if (list[j] === m[i].element[0]) {
m[i].proportions().height = 0;
continue droppablesLoop;
}
}
m[i].visible = m[i].element.css("display") !== "none";
if (!m[i].visible) {
continue;
}
if (type === "mousedown") {
m[i]._activate.call(m[i], event);
}
m[i].offset = m[i].element.offset();
m[i].proportions({
width: m[i].element[0].offsetWidth * percent,
height: m[i].element[0].offsetHeight * percent
});
}
};
Enable the element to be resizable on a scaled container
function resizeFix(event, ui) {
var changeWidth = ui.size.width - ui.originalSize.width,
newWidth = ui.originalSize.width + changeWidth / percent,
changeHeight = ui.size.height - ui.originalSize.height,
newHeight = ui.originalSize.height + changeHeight / percent;
ui.size.width = newWidth;
ui.size.height = newHeight;
}
Makes it so drag works on a scaled container
function dragFix(event, ui) {
var containmentArea = $("#documentPage_"+ui.helper.parent().parent().attr('id').replace(/^(\w+)_/, "")),
contWidth = containmentArea.width(), contHeight = containmentArea.height();
ui.position.left = Math.max(0, Math.min(ui.position.left / percent , contWidth - ui.helper.width()));
ui.position.top = Math.max(0, Math.min(ui.position.top / percent, contHeight- ui.helper.height()));
}
Creating a draggable element that I can drag onto the box.
.directive('draggableTypes', function() {
return {
restrict:'A',
link: function(scope, element, attrs) {
element.draggable({
zIndex:3000,
appendTo: 'body',
helper: function(e, ui){
var formBox = angular.element($("#formBox"));
percent = formBox.width() / scope.templateData.pdf_width;
if(element.attr('id') == 'textbox_item')
return $('<div class="text" style="text-align:left;font-size:14px;width:200px;height:20px;line-height:20px;">New Text Box.</div>').css({ 'transform': 'scale(' + percent + ')', '-moz-transform': 'scale(' + percent + ')', '-webkit-transform': 'scale(' + percent + ')', '-ms-transform': 'scale(' + percent + ')'});
if(element.attr('id') == 'sm_textbox_item')
return $('<div class="text" style="text-align:left;font-size:14px;width:5px;height:5px;line-height:20px;"></div>').css({ 'transform': 'scale(' + percent + ')', '-moz-transform': 'scale(' + percent + ')', '-webkit-transform': 'scale(' + percent + ')', '-ms-transform': 'scale(' + percent + ')'});
}
});
}
};
})
Create draggable/resizable elements that may already be in the box and applying the drag/resize fix to these
.directive('textboxDraggable', function() {
return {
restrict:'A',
link: function(scope, element, attrs) {
element.draggable({
cursor: "move",
drag: dragFix,
start: function(event, ui) {
var activeId = element.attr('id');
scope.activeElement.id = activeId;
scope.activeElement.name = scope.templateItems[activeId].info.name;
scope.$apply();
}
});
element.resizable({
minWidth: 25,
minHeight: 25,
resize: resizeFix,
stop: function( event, ui ) {
var activeId = element.attr('id');
scope.activeElement.duplicateName = false;
scope.activeElement.id = activeId;
scope.activeElement.name = scope.templateItems[activeId].info.name;
scope.templateItems[activeId]['style']['width'] = element.css('width');
scope.templateItems[activeId]['style']['height'] = element.css('height');
scope.$apply();
}
})
}
};
})
What happens when an item is dropped
.directive('droppable', function($compile) {
return {
restrict: 'A',
link: function(scope,element,attrs){
element.droppable({
drop:function(event,ui) {
var draggable = angular.element(ui.draggable),
draggable_parent = draggable.parent().parent(),
drag_type = draggable.attr('id'),
documentBg = element,
x = ui.offset.left,
y = ui.offset.top,
element_top = (y - documentBg.offset().top - draggable.height() * (percent - 1) / 2) / percent,
element_left = (x - documentBg.offset().left - draggable.width() * (percent - 1) / 2) / percent,
timestamp = new Date().getTime();
//just get the document page of where the mouse is if its a new element
if(draggable_parent.attr('id') == 'template_builder_box_container' || draggable_parent.attr('id') == 'template_builder_container')
var documentPage = documentBg.parent().parent().attr('id').replace(/^(\w+)_/, "");
//if you are dragging an element that was already on the page, get parent of draggable and not parent of where mouse is
else var documentPage = draggable_parent.parent().parent().attr('id').replace(/^(\w+)_/, "");
if(drag_type == "textbox_item")
{
scope.activeElement.id = scope.templateItems.push({
info: {'page': documentPage,'name': 'textbox_'+timestamp, 'type': 'text'},
style: {'text-align':'left','font-size':'14px','top':element_top+'px','left':element_left+'px', 'width':'200px', 'height':'20px'}
}) - 1;
scope.activeElement.name = 'textbox_'+timestamp;
}
else if(drag_type == "sm_textbox_item")
{
scope.activeElement.id = scope.templateItems.push({
info: {'page': documentPage,'name': '', 'type': 'text'},
style: {'text-align':'left','font-size':'14px','top':element_top+'px','left':element_left+'px', 'width':'5px', 'height':'5px'}
}) - 1;
scope.activeElement.name = 'textbox_'+timestamp;
}
else {
scope.templateItems[scope.activeElement.id]['style']['top'] = draggable.css('top');
scope.templateItems[scope.activeElement.id]['style']['left'] = draggable.css('left');
}
scope.$apply();
}
});
}
};
})
last but not least, my controller
.controller('testing', function($scope, $rootScope, $state, $stateParams) {
$scope.templateItems = [];
$scope.activeElement = { id: undefined, name: undefined };
$scope.templateData = {"id":"12345", "max_pages":1,"pdf_width":385,"pdf_height":800};
$scope.clickElement = function(index) { $scope.activeElement = { id: index, name: $scope.templateItems[index].info.name } }
});
Here is the basis of my html
<div id="formBox" ng-style="formbox(templateData.pdf_width)" zoom>
<div class="trimSpace" ng-style="trimSpace(templateData.pdf_width)" zoom>
<div id="formScale" ng-style="formScale(templateData.pdf_width)" zoom>
<form action="#" id="{{ templateData.id }}_form">
<div ng-repeat="key in [] | range:templateData.max_pages">
<div class="formContainer" id="{{ templateData.id + '_' + (key+1) }}" ng-style="{width: templateData.pdf_width+'px', height: templateData.pdf_height+'px'}">
<div class="formContent">
<div class="formBackground" id="documentPage_{{ (key+1) }}" droppable>
<div ng-hide="preview" ng-repeat="item in templateItems">
<div ng-if="item.info.page == (key+1) && item.info.type == 'text'" id="{{ $index }}" data-type="{{ item.info.type }}" ng-click="clickElement($index)" class="text" ng-style="item.style" textbox-draggable>{{ item.info.name }}</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
For the cursor position while dragging, see this answer : Make Cursor position in center for ui.helper in jquery-ui draggable method
Basically, you can control the cursor position of the instance, allowing to have something more dynamic that cursorAt. Like this:
start: function(event, ui){
$(this).draggable('instance').offset.click = {
left: Math.floor(ui.helper.width() / 2),
top: Math.floor(ui.helper.height() / 2)
}
},
Then on the drop, you need to take into account the transform, but you can simplify by using the helper coordinates instead of the draggable. Like this:
element_top = (ui.helper.offset().top / percent) - (documentBg.offset().top / percent);
element_left = (ui.helper.offset().left / percent) - (documentBg.offset().left / percent);
Result: https://codepen.io/anon/pen/jamLBq
It looks like what is causing this to look strange is the following:
First, the small div is styled as display: block. This means that even though it looks like the div is small, that element actually stretches out to it's whole container.
Second, once you show the dragged square on the left screen, the relation between the mouse cursor and the element whole is technically centered, but you are cutting the size of the original element to a smaller one, and when the width and height get diminished, the result is rendered with the new width and height starting from the upper left corner of the original div. (If you style the small button to be display: inline, you can see what I mean. Try grabbing it from the upper left corner and the try the lower right one. You will see that the former looks fine but the latter is off).
So my suggestions are:
Make the draggabble elements display: inline
Make the dragged element on the left screen the exact height and width of the original element on the right screen.
Hope that helps!
I've forked your codepen and played around with it.
Take a look at it HERE, and see if it helps you find the "bug".
For your draggable script, I changed the code to this, adding margin-left and margin-right:
if(element.attr('id') == 'sm_textbox_item') { /* the small draggable box */
var el = {
pos: element.offset(), // position of the small box
height: element.outerHeight() + 20,
left: 0
}
var deduct = $('#formBox').innerWidth() - 20; // width of the element that's left of small box's container
el.left = el.pos.left - deduct;
return $('<div class="text" style="text-align:left; font-size:14px; width:5px; height:5px; line-height:20px;"></div>')
.css({
'margin-left': el.left + 'px',
'margin-top': el.pos.top - el.height + 'px',
'transform': 'scale(' + percent + ')',
'-moz-transform': 'scale(' + percent + ')',
'-webkit-transform': 'scale(' + percent + ')',
'-ms-transform': 'scale(' + percent + ')'
});
}
Then, for your droppable script, I changed the formula for element_top and element_left:
// old formula
element_top = (y - documentBg.offset().top - draggable.height() * (percent - 1) / 2) / percent
element_left = (x - documentBg.offset().left - draggable.width() * (percent - 1) / 2) / percent
// new formula
element_top = (y - documentBg.offset().top) / (percent * 0.915)
element_left = (x - documentBg.offset().left) / (percent * 0.915)
It gives an "almost" accurate result, but you may be able to tweak it further to polish it. Hope this helps.
For attaching elements with cursor during dragging you just need to use
cursorAt: { top: 6, left: -100 }
And a little change in top and left parameters of "sm_textbox_item".
top: (y - documentBg.offset().top) / (percent) + "px",
left: (x - documentBg.offset().left) / (percent) + "px",
For the large box again some tweak in top and left element is required (pen updated).
top: element_top-3,
left: element_left+6.49,
I forked your pen and did some changes. I know that this is not a perfect solution, i am also trying to solve this bit by bit. You can check it here
#ITWitch is right, there have to be some bug in draggable().
Style margin: 0 auto; in #sm_textbox_item is the source of problem.
Try to add this to draggable options in your draggableType directive to correct the position:
cursorAt: {left: -parseInt(window.getComputedStyle(element[0],null,null)['margin-left'])},
This problem occurs when you add a transform to a element's style, then make it draggable. You'll have to make do without transform to have a perfect result. I spent 2 days debugging till I found it out, and I didn't want someone else to go through that pain.
Can you please take a look at this Demo and let me know how I can change the
use of the .css() rule of background-position-x event ONLY when the mouse is moving to Left or Right on the .homeSlide ? what is happening now is As soon as the mouse enters into the .homeSlide jquery is running the CSS rule but I want to do it only when the mouse is moving
jQuery('.homeSlider').mousemove(function(move){
var moveMouse = (move.pageX * -1 / 3);
jQuery('.homeSlider .slide').css({
'background-position-x': moveMouse + 'px'
});
});
jQuery('.homeSlider').mouseleave(function(){
jQuery('.homeSlider .slide').animate({
'background-position-x': '0'
});
});
.homeSlider {width: 100%; height: 400px; background: red;}
.homeSlider .slide {width:100%; height: 100%; background: url(http://1.bp.blogspot.com/-JHKDpZ5orFc/T0523m_rAlI/AAAAAAAAAEU/zYZv__hk74I/s1600/Panorama_by_erikcollinder.jpeg) 0 0;}
.slide {transition: background-position-x 0.5s;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="homeSlider">
<div class="slide"></div>
</div>
The issue is when mouse enters the image element, it already has some x position, so the image moves by x. So we need to decide a starting point for x position.
Try this code:
var startingPos = 0;
jQuery('.homeSlider').mousemove(function(move){
var moveMouse = (move.pageX * -1 / 3);
if(startingPos == 0){
startingPos = moveMouse;
return;
} else {
moveMouse = moveMouse-startingPos;
}
jQuery('.homeSlider .slide').css({
'background-position-x': moveMouse + 'px'
});
});
jQuery('.homeSlider').mouseleave(function(){
startingPos = 0;
jQuery('.homeSlider .slide').animate({
'background-position-x': '0'
});
});
Try and put on the left and right side an "<" or ">" image href on them make a hover action that will modify the position of the picture the way you want it to react.
Here is an example:
http://tympanus.net/Blueprints/FullWidthImageSlider/
Try changing the background-position-x 0.5s to background-position-x 0.0s. Check
this
fiddle which is a combination with the other answer user3580410 provides
Edit
The thing that was bothering me, was that if you drag the div all the way to the bottom right, then you make the browsers size larger, the div isn't within its parents view, until you actually drag it. Once you drag it, it snaps back within its parents view.
How can I make the div stay in its parent view even after you resize the browser?
JSFiddle
See code snippet bellow.
Older
I have a div which I made draggable through JQuery UI. I want it to be positioned with a percentage value. This way, when you resize the browser, the div will be relatively, or proportionally at the same position.
I checked out this answer, and followed what it said. When I output the left and top position, the numbers were not accurate. When I drag the div all the way to the bottom right, it gives me the following output:
66.55518394648828%
92.71255060728744%
Actually, it does depend on the window size, but the point is, the numbers aren't 100% for left and right.
How can I keep the div at the same position proportionally, when the browser resizes?
Relevant Code:
stop: function () {
console.log(parseInt($(this).css("left")) / (wrapper.width() / 100) + "%");
console.log(parseInt($(this).css("top")) / (wrapper.height() / 100) + "%");
}
JSFiddle
Code Snippet
var wrapper = $('#fixed');
var dragDiv = $('#draggable');
dragDiv.css({
'top': ($(window).height() / 2) - (dragDiv.outerHeight() / 2),
'left': ($(window).width() / 2) - (dragDiv.outerWidth() / 2)
});
dragDiv.draggable({
containment: "parent", // <- keep draggable within fixed overlay
stop: function() {
$(this).css("left", parseInt($(this).css("left")) / (wrapper.width() / 100) + "%");
$(this).css("top", parseInt($(this).css("top")) / (wrapper.height() / 100) + "%");
console.log(parseInt($(this).css("left")) / (wrapper.width() / 100) + "%");
console.log(parseInt($(this).css("top")) / (wrapper.height() / 100) + "%");
}
});
body {
/*width: 2000px;
height: 2000px;*/
background-image: url("http://www.freevector.com/site_media/preview_images/FreeVector-Square-Patterns-Set.jpg");
}
#fixed {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.25)
}
#draggable {
color: lightblue;
background-color: red;
width: 200px;
position: absolute;
}
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.4.js"></script>
<script type="text/javascript" src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<div id="fixed">
<div id="draggable">Drag Me!</div>
</div>
The problem is the the parent container to the dragable element has the fixed position.
You can't position items relative to a fixed element.
$(this).css("left") // this refers to $('#draggable')
This is not giving you a left position relative to the fixed container, but rather the body element which has padding on it. So either set the html, body, and #fixed container to have a height and width of 100% and make them position relative.
OR
Remove the padding and margin from your body and html element
body, html{ margin: 0px; padding: 0px;}
Fiddle with body, html, and #fixed at 100%
Fiddle with 0 padding and margin
EDIT
I created a new event to fire every time the window is resized. The event checks to see if the dragable div is outside the bounds of the window. Adjusting as necessary to keep the element in view. I used the code WITHOUT the fixed element because it is not necessary. Let me know if you have any questions.
JS
$(window).resize(function () {
if ($(window).innerWidth() > $(dragDiv).width()) {
var oLeft = parseInt($(window).innerWidth() - $(dragDiv).width());
var posLeft = parseInt($(dragDiv).css("left"));
if (posLeft > oLeft) {
$(dragDiv).css("left", oLeft);
toPercent();
}
}
if ($(window).innerHeight() > $(dragDiv).height()) {
var oTop = parseInt($(window).innerHeight() - $(dragDiv).height());
var posTop = parseInt($(dragDiv).css("top"));
if (posTop > oTop) {
$(dragDiv).css("top", oTop);
toPercent();
}
}
});
function toPercent() {
$(dragDiv).css("left", parseInt($(dragDiv).css("left")) / (wrapper.innerWidth() / 100) + "%");
$(dragDiv).css("top", parseInt($(dragDiv).css("top")) / (wrapper.innerHeight() / 100) + "%");
}
Updated Fiddle for question part 2
I make a jquery tooltip but have problem with it, when mouse enter on linke "ToolTip" box tooltip don't show in next to link "ToolTip" it show in above linke "ToolTip" , how can set it?
Demo: http://jsfiddle.net/uUwuD/1/
function setOffset(ele, e) {
$(ele).prev().css({
right: ($(window).width() - e.pageX) + 10,
top: ($(window).height() - e.pageY),
opacity: 1
}).show();
}
function tool_tip() {
$('.tool_tip .tooltip_hover').mouseenter(function (e) {
setOffset(this, e);
}).mousemove(function (e) {
setOffset(this, e);
}).mouseout(function () {
$(this).prev().fadeOut();
});
}
tool_tip();
Something like this works, you've still got a bug where the tooltip sometimes fades away on the hover of a new anchor. I'll leave you to fix that, or for another question.
function setOffset(ele, e) {
var tooltip = $(ele).prev();
var element = $(ele);
tooltip.css({
left: element.offset().left - element.width() - tooltip.width(),
top: element.offset().top - tooltip.height(),
opacity: 1
}).show();
}
And here's the jsFiddle for it: http://jsfiddle.net/uUwuD/4/
you need to calculate the window width and minus it with the width of your tooltip and offset
if(winwidth - (offset *2) >= tooltipwidth + e.pageX){
leftpos = e.pageX+offset;
} else{
leftpos = winwidth-tooltipwidth-offset;
}
if you want more detail please refer :)
I have a list of items & they are holding images, each image is 800w x 600 H. The original div height is 800 W x 300 H. I figured out how to expand the div when it is clicked, but i want to know how to collapse it when you clicked it while it is already expanded. Right now i just expands the div even further
$('.expand').bind('click', function() {
var currHeight = $(this).css('height').replace(/px/,'');
currHeight = currHeight * 1;
var newHeight = currHeight + 500;
$(this).animate({
height: newHeight
},1000);
});
any idea on how to create an if else statement that would say, IF the div is already expanded then collapse on click, or if the div is collapse, then expand to # of px.
You can detect the current height and branch:
$('.expand').bind('click', function() {
var $this = $(this),
height = $this.height();
if (height > 500) {
height -= 500;
}
else {
height += 500;
}
$this.animate({
height: height
},1000);
});
I've done a couple of other things in there. You can use height rather than css('height') to get the value without units, and no need for the * 1 trick. I've also done the $(this) once and reused it, since there are multiple function calls and an allocation involved when you call the $() function. (It doesn't matter here, but it's a good habit to get into provided you're not caching it longer than you mean to [via a closure or such].)
Alternately, you can remember that you've done it another way (using the data feature):
$('.expand').bind('click', function() {
var $this = $(this),
height = $this.height(),
expanded = $this.data('expanded');
if (expanded) {
height -= 500;
}
else {
height += 500;
}
$this.data('expanded', !expanded);
$this.animate({
height: height
},1000);
});
Or combine those to store the original height in case it gets influenced by something else:
$('.expand').bind('click', function() {
var $this = $(this),
height = $this.height(),
prevHeight = $this.data('prevHeight');
if (prevHeight) {
height = prevHeight;
$this.data('prevHeight', undefined);
}
else {
$this.data('prevHeight', height);
height += 500;
}
$this.animate({
height: height
},1000);
});
Take your pick!
I would use CSS to set the height of the div in both original and expanded versions, and then when the div is clicked, toggle a class to change the height:
/* CSS for the height */
.expand {
height: 300px;
}
.expand.expanded {
height: 600px;
}
and then in the click method, just:
$(this).toggleClass("expanded");
A few pointers with jQ:
.click(function(){})
.css({height: "300*1px"}) // Why are you multiplying Anything by ONE?!
And you can use
if($(this).css("height") == "300px") {
Do stuff
} else {
Do other stuff
}
Edit: But other options above are far better.
You can check the .height() at the time of the click event and .animate() the height += or -= accordingly (something .animate() supports), like this:
$('.expand').click(function() {
$(this).animate({
height: $(this).height() > 300 ? "+=500px" : "-=500px"
}, 1000);
});
Or, use .toggle(), like this:
$('.expand').toggle(function() {
$(this).animate({ height: "+=500px" }, 1000);
}, function() {
$(this).animate({ height: "-=500px" }, 1000);
});
Pure Jquery, check the documentation Jquery Animate
$( "#clickme" ).click(function() {
$( "#book" ).animate({
height: "toggle"
}, {
duration: 5000,
specialEasing: {
height: "linear"
},
complete: function() {
//DO YOUR THING AFTER COMPLETE
}
});
});