Posting this answer for the moment might return to update it once I have worked out the "bugs", yet this does do what you are asking about.
Current Working Example: https://jsfiddle.net/Twisty/npmtfLt6/22/
HTML
<div id="box_wrapper">
<div id="rotate_limit"></div>
<div id="rotate_handle"></div>
<div id="image_box"></div>
</div>
dial.offset().left: <span id="left"></span>
<br> dial.offset().top: <span id="top"></span>
<br> dial.centerX: <span id="centerX"></span>
<br> dial.centerY: <span id="centerY"></span>
<br> pageX: <span id="pageX"></span>
<br> pageY: <span id="pageY"></span>
<br> offset: <span id="offset"></span>
<br> newOffset: <span id="newOffset"></span>
<br> RAD2DEG: <span id="RAD2DEG"></span>
<br> r: <span id="r"></span>
<br>
First, we have to separate the handle so that we can move it freely. We also need to contain it within a wrapper. I have added the #canvas
for now, yet as I mentioned in my comment, I think it can be accomplished without this element.
CSS
#box_wrapper {
position: relative;
margin-left: 180px;
margin-top: 100px;
width: 180px;
height: 180px;
}
#image_box {
background: url('https://placehold.it/80x80/c9112d/fff&text=Image');
background-size: 100% 100%;
position: absolute;
top: 40px;
left: 40px;
width: 100px;
height: 100px;
border: 4px solid white;
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
#image_box:hover {
border: 4px solid black;
}
#rotate_limit {
background: transparent;
position: absolute;
height: 180px;
width: 180px;
top: 0;
left: 0;
-webkit-border-radius: 180px;
-moz-border-radius: 180px;
border-radius: 180px;
border: dashed #ccc 1px;
z-index: -1;
}
#rotate_handle {
background: url('https://s32.postimg.org/re2hwf3fp/rotate_handle_down.png') no-repeat;
position: absolute;
background-size: 100% 100%;
left: 18px;
top: 18px;
width: 20px;
height: 20px;
}
.ui-resizable-handle {
border: 3px solid greenyellow;
cursor: pointer;
}
Minor changes here for new elements and how we position them in our wrapper.
jQuery
var RAD2DEG = 180 / Math.PI;
var dial = $("#image_box");
var pointerEl = $("#rotate_handle")[0];
var canvasEl = $("#rotate_limit")[0];
var canvas = {
width: canvasEl.offsetWidth,
height: canvasEl.offsetHeight,
top: canvasEl.offsetTop,
left: canvasEl.offsetLeft
};
canvas.center = [canvas.left + canvas.width / 2, canvas.top + canvas.height / 2];
canvas.radius = canvas.width / 2;
pointerEl.ondragstart = function() {
return false;
};
function limit(x, y) {
x = x - canvas.center[0];
y = y - canvas.center[1];
var radians = Math.atan2(y, x);
return {
x: Math.cos(radians) * canvas.radius + canvas.center[0],
y: Math.sin(radians) * canvas.radius + canvas.center[1]
};
}
$('#left').text(dial.offset().left);
$('#top').text(dial.offset().top);
dial.centerX = dial.offset().left + dial.width() / 2;
dial.centerY = dial.offset().top + dial.height() / 2;
$('#centerX').text(dial.centerX);
$('#centerY').text(dial.centerY);
var offset, rotate = false;
pointerEl.onmousedown = function(e) {
rotate = true;
offset = Math.atan2(dial.centerY - e.pageY, e.pageX - dial.centerX);
$('#pageX').text(e.pageX);
$('#pageY').text(e.pageY);
$('#offset').text(offset);
};
$(document).mouseup(function() {
rotate = false;
});
$(document).mousemove(function(e) {
if (rotate) {
var newOffset = Math.atan2(dial.centerY - e.pageY, e.pageX - dial.centerX);
$('#newOffset').text(newOffset);
var r = (offset - newOffset) * RAD2DEG;
$('#RAD2DEG').text(RAD2DEG);
$('#r').text(r);
dial.css('-webkit-transform', 'rotate(' + r + 'deg)');
dial.css('transform', 'rotate(' + r + 'deg)');
var result = limit(e.pageX, e.pageY);
pointerEl.style.left = result.x + "px";
pointerEl.style.top = result.y + "px";
}
});
dial.resizable({
handles: 'ne, nw, se, sw'
});
$("#box_wrapper").draggable({
handle: dial
});
Not a lot of changes to your personal code, since the math works. Just a few organization changes. Since you want to drag all of this about, we make the wrapper draggable, but set the handle to be #image_box
. This allows us to move the box when we grab it, but leaves the rotate handle free to move independently. This way we can rotate without dragging the whole element.
When the rotate handle is moved, it is constrained (mostly) and the pageX
and pageY
are then used to calculate the transformation.
The positioning of the rotate handle is not as smooth as the rotation itself. You might have some insight on that, but this does achieve the function you needed thus far. If you desire, we can work on improving it.
See Also:
UPDATE
Corrected the "bugs". Working example: https://jsfiddle.net/Twisty/npmtfLt6/28/
- Improved
calcDeg()
for this specific use case:
function calcDeg(e){
// Retrieve degree from current handle position vs. circle center
var mPos = {
x : e.x - canvas.center[0],
y : e.y - canvas.center[1]
};
var getAtan = Math.atan2(mPos.y, mPos.x);
return getAtan*180/Math.PI;
}
- Rounded degree value for CSS:
var r = calcDeg(result);
$('#r').text(Math.round(r));
dial.css({
'-webkit-transform': 'rotate(' + Math.round(r) + 'deg)',
'transform': 'rotate(' + Math.round(r) + 'deg)'
});
- Adjusted pointer styling and position to be more dynamic:
#rotate_handle {
background: url('https://s32.postimg.org/re2hwf3fp/rotate_handle_down.png') no-repeat;
position: absolute;
background-size: 100% 100%;
left: 100%;
top: 50%;
margin-top: -10px;
margin-left: -10px;
width: 20px;
height: 20px;
}
- Adjusted Resize such that limit and wrapper are adjusted with image:
dial.resizable({
handles: 'ne, nw, se, sw',
alsoResize: "#box_wrapper, #rotate_limit"
});
Resizing works, yet I would advise setting aspectRatio: true
. This will keep the image square and this allow the limiter to become oblong or turn into an ellipse.
This may not work for your needs, so you may want to consider writing a function to be executed during resize
that could increase the width
, height
, and border-radius
of the wrapper and limiter properly.
I hope this fully answers your question.