I am trying to build an interface that allows both resize/drag and rotate on some element and to achieve this I am using interact.js javascript library.
I have my interact functions working:
interact('.resize-drag-ratio')
.draggable({
onmove: window.dragMoveListener
})
.resizable({
preserveAspectRatio: true,
edges: { left: true, right: true, bottom: true, top: true }
})
.on('resizemove', function (event) {
var target = event.target,
x = (parseFloat(target.getAttribute('data-x')) || 0),
y = (parseFloat(target.getAttribute('data-y')) || 0);
var min_size = 35;
if(event.rect.width>min_size){
// update the element's style
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.webkitTransform = target.style.transform =
'translate(' + x + 'px,' + y + 'px)';
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
});
and drag-rotate that allows for rotation
interact('.drag-rotate')
.draggable({
onstart: function (event) {
const element = event.target;
const rect = element.getBoundingClientRect();
// store the center as the element has css `transform-origin: center center`
element.dataset.centerX = rect.left + rect.width / 2;
element.dataset.centerY = rect.top + rect.height / 2;
console.log("element.dataset.centerX: "+element.dataset.centerX);
console.log("element.dataset.centerY: "+element.dataset.centerY);
// get the angle of the element when the drag starts
element.dataset.angle = getDragAngle(event);
},
onmove: function (event) {
var element = event.target;
var center = {
x: 300,
y: 300,
};
console.log("element.dataset.centerX: "+element.dataset.centerX);
console.log("element.dataset.centerY: "+element.dataset.centerY);
var angle = getDragAngle(event);
// update transform style on dragmove
element.style.transform = 'rotate(' + angle + 'rad' + ')';
},
onend: function (event) {
const element = event.target;
// save the angle on dragend
element.dataset.angle = getDragAngle(event);
},
})
The two classes get switched using jQuery thus turning drag into rotation and vice versa.
My problem is that object location and rotation angle do not stay as placed and I am not sure how to fix that.
After i drag an element to some position and press the rotate button, one I start rotating the element it moves to top:0px left:0px and does not stay at its dragged position.
you can see the full working code right here: https://codepen.io/yaary-vidanpeled/pen/ZZwGmE
This is happening because each time you apply css, you are overwriting your previous styles.
Here is an example, let's say you have a text ( #text
element ) with color
of red
, now you want to change it with JavaScript.
document.getElementById('text').style.color = 'green';
what actually happened here ? whatever was assigned as the color property of the style object is now overwritten.
The same thing is happening when you are writing (in your interact initialisation of .resize-drag-ratio
):
target.style.transform = 'translate(' + x + 'px,' + y + 'px)';
And overwriting the translate
again by writing (in your interact initialisation of .drag-rotate
)
element.style.transform = 'rotate(' + angle + 'rad' + ')';
Remember that the rotate()
and translate()
both are values of the css translate
property.
You should somehow preserve the all these rotation angle, and translate values. (Looks like you already have data-attribtues
for them so it wont be hard)
And apply the value of the element.style.transform
as following:
target.style.transform = 'translate(' + x + 'px,' + y + 'px) rotate(' + angle + 'rad)';
Note: Your snippet has the function dragMoveListener(event) {
declared twice.
Working Snippet:
console.log('start');
//function isEven
function isEven(n) {
return n == parseFloat(n) ? !(n % 2) : void 0;
}
interact('.resize-drag-ratio')
.draggable({
onmove: window.dragMoveListener
})
.resizable({
preserveAspectRatio: true,
edges: {
left: true,
right: true,
bottom: true,
top: true
}
})
.on('resizemove', function(event) {
var target = event.target,
x = (parseFloat(target.getAttribute('data-x')) || 0),
y = (parseFloat(target.getAttribute('data-y')) || 0);
rotation = (parseFloat(target.getAttribute('data-angle')) || 0)
var min_size = 35;
if (event.rect.width > min_size) {
// update the element's style
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.webkitTransform = target.style.transform =
'translate(' + x + 'px,' + y + 'px) rotate(' + rotation + 'rad)';
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
});
interact('.resize-drag')
.draggable({
onmove: window.dragMoveListener
})
.resizable({
preserveAspectRatio: false,
edges: {
left: true,
right: true,
bottom: true,
top: true
}
})
.on('resizemove', function(event) {
var target = event.target,
x = (parseFloat(target.getAttribute('data-x')) || 0),
y = (parseFloat(target.getAttribute('data-y')) || 0),
rotation = (parseFloat(target.getAttribute('data-angle')) || 0)
//console.log("event.rect.width: "+event.rect.width);
//prevents resizing to units smaller then 35px
var min_size = 35;
if (event.rect.width > min_size) {
// update the element's style
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
//$("#form_bubble_width").val(event.rect.width);
//$("#form_bubble_width").val(event.rect.height);
// translate when resizing from top or left edges
x += event.deltaRect.left;
y += event.deltaRect.top;
target.style.webkitTransform = target.style.transform =
'translate(' + x + 'px,' + y + 'px) rotate(' + rotation + 'rad)';
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
//target.textContent = event.rect.width + '×' + event.rect.height;
}
});
// target elements with the "draggable" class
interact('.draggable')
.draggable({
// enable inertial throwing
inertia: true,
// keep the element within the area of it's parent
restrict: {
restriction: "parent",
endOnly: true,
elementRect: {
top: 0,
left: 0,
bottom: 1,
right: 1
}
},
// enable autoScroll
autoScroll: true,
// call this function on every dragmove event
onmove: dragMoveListener,
// call this function on every dragend event
onend: function(event) {
// var textEl = event.target.querySelector('p');
console.log(event.target.id)
var distance = (Math.sqrt(Math.pow(event.pageX - event.x0, 2) +
Math.pow(event.pageY - event.y0, 2) | 0))
.toFixed(2) + 'px';
}
});
interact('.resize-drag , .resize-drag-ratio').on('tap', function(event) {
event.preventDefault();
var target = event.target
console.log("tap resize-drag class element");
var uuid = target.id;
//console.log("uuid: "+uuid);
console.log("click");
});
function dragMoveListener(event) {
var target = event.target,
// keep the dragged position in the data-x/data-y attributes
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy,
rotation = (parseFloat(target.getAttribute('data-angle')) || 0);
// translate the element
target.style.webkitTransform =
target.style.transform =
'translate(' + x + 'px, ' + y + 'px) rotate(' + rotation + 'rad)';
// update the posiion attributes
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
target.setAttribute('data-angle', rotation);
}
// this is used later in the resizing and gesture demos
window.dragMoveListener = dragMoveListener;
var mouseX = 0,
mouseY = 0
//function onMousemove(e)
function onMousemove(e) {
var m_posx = 0,
m_posy = 0,
e_posx = 0,
e_posy = 0,
obj = this;
//get mouse position on document crossbrowser
if (!e) {
e = window.event;
}
if (e.pageX || e.pageY) {
m_posx = e.pageX;
m_posy = e.pageY;
} else if (e.clientX || e.clientY) {
m_posx = e.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
m_posy = e.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
//get parent element position in document
if (obj.offsetParent) {
do {
e_posx += obj.offsetLeft;
e_posy += obj.offsetTop;
} while (obj = obj.offsetParent);
}
// mouse position minus elm position is mouseposition relative to element:
dbg.innerHTML = ' X Position: ' + (m_posx - e_posx) +
' Y Position: ' + (m_posy - e_posy);
mouseX = (m_posx - e_posx);
mouseY = (m_posy - e_posy);
}
var elem = document.getElementById('container');
//elem.addEventListener('mousemove', onMousemove, false);
var dbg = document.getElementById('dbg'); //just for debug div instead of console
$(document).ready(function() {
var is_rotate = true;
$("#btn_rotate").click(function() {
// console.log('ddd');
if (is_rotate) {
$(this).text('drag-resize');
$(".element").removeClass("drag-rotate");
$(".element").addClass("resize-drag-ratio");
is_rotate = false;
} else {
$(this).text('rotate');
$(".element").removeClass("resize-drag-ratio");
$(".element").addClass("drag-rotate");
is_rotate = true;
}
//console.log('click: '+is_rotate);
});
var saved_mouseX = 0;
var saved_mouseY = 0;
//interact("#container").on('tap', function (event) {
interact("#container").on('tap', function(event) {
event.preventDefault();
var target = event.target
if (target.id == "tp_image") {
console.log(target.id);
console.log(mouseX + "-" + mouseY);
saved_mouseX = mouseX;
saved_mouseY = mouseY;
//$('#modal_stickers').modal('show');
}
});
//interact('.drag-rotate')
interact('.drag-rotate')
.draggable({
onstart: function(event) {
const element = event.target;
const rect = element.getBoundingClientRect();
// store the center as the element has css `transform-origin: center center`
element.dataset.centerX = rect.left + rect.width / 2;
element.dataset.centerY = rect.top + rect.height / 2;
// console.log("element.dataset.centerX: " + element.dataset.centerX);
// console.log("element.dataset.centerY: " + element.dataset.centerY);
// get the angle of the element when the drag starts
element.dataset.angle = getDragAngle(event);
},
onmove: function(event) {
var element = event.target;
var center = {
x: 300,
y: 300,
};
// console.log("element.dataset.centerX: " + element.dataset.centerX);
// console.log("element.dataset.centerY: " + element.dataset.centerY);
var angle = getDragAngle(event);
var x = element.dataset.x;
var y = element.dataset.y;
// update transform style on dragmove
// this is where the bug was; at initial point, there was no x, or y position set on the dataset of the element. thus your style value would be undefined, so here we check the values of x and y first and set the style accordingly;
if (typeof x != 'undefined' && typeof y != 'undefined') {
element.style.transform = 'translate(' + x + 'px, ' + y + 'px) rotate(' + angle + 'rad' + ')';
} else {
element.style.transform = 'rotate(' + angle + 'rad' + ')';
}
},
onend: function(event) {
const element = event.target;
// save the angle on dragend
element.dataset.angle = getDragAngle(event);
},
})
//function getDragAngle(event)
function getDragAngle(event) {
var element = event.target;
var startAngle = parseFloat(element.dataset.angle) || 0;
var center = {
x: parseFloat(element.dataset.centerX) || 0,
y: parseFloat(element.dataset.centerY) || 0,
};
var angle = Math.atan2(center.y - event.clientY,
center.x - event.clientX);
return angle - startAngle;
}
});
#btn_rotate {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
background: #ccc;
padding: 30px;
}
.element {
width: 25%;
min-height: 6.5em;
margin: 10%;
background-color: #29e;
color: white;
/* added later */
touch-action: none;
box-sizing: border-box;
}
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.0.js"></script>
<script type="text/javascript" src="https://unpkg.com/interactjs@next/dist/interact.js"></script>
<div class="element drag-rotate">
<p> drag to rotate</p>
</div>
<div id="btn_rotate">rotate
</div>
Check line 288 on the script part. there is a comment explaining about the if
block and why this was happening.