I have been asked, using Konvajs, to work out an animation that will rotate a circle as if spinning on its central x-axis. So imagine a coin spinning on a table. The intention is to reveal some text on the circle. At the start the circle is fully visible as if from behind so no text visible, then it flips to reveal the text.
I have this code that does a rotation like a spinning wheel.
Can anyone give me a tween / animation approach that would achieve the spinning coin effect?
// the tween has to be created after the node has been added to the layer
var tween = new Konva.Tween({
node: group,
duration: 4,
rotation: 360,
easing: Konva.Easings.BackEaseOut
}
});
tween.play();
After some research it looks like a 3D spin requires heavier lifting which may not be available or work well on mobile.
A good second-best appears to be using scaleX and animating from 0 > 1.
group.scaleX(0);
var tween = new Konva.Tween({
node: group,
duration: .25,
scaleX: 1,
easing: Konva.Easings.EaseOut
});
Here is an example of the second-best version using scaleX() effect. Because of the need to calculate scaleX() and control visibility of the text so as to make it appear that the disc is solid, I moved away from a tween and over to an animation().
// Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container1', width: 300, height: 200});
// Add a layer for line
var layer = new Konva.Layer({draggable: false});
s1.add(layer);
// just a plain JS object to keep common variables in hand.
var cfg = { w: 300, h: 200, r: 80, txtSize: 520};
var group = new Konva.Group();
var circle = new Konva.Circle({x: cfg.w/2, y: cfg.h/2, radius: cfg.r, fill: 'DodgerBlue', stroke: 'DeepPink', strokeWidth: 5})
group.add(circle)
var textValue = new Konva.Text({
id: "t1",
x: cfg.w/2,
y: cfg.h/2,
text: '',
fill: 'DeepPink ',
fontSize: cfg.txtSize
});
group.add(textValue);
textValue.offset({x: textValue.getWidth()/2, y: textValue.getHeight()/2});
layer.add(group)
// to spin a group about a point, set the offset to that point, then set the x & y to that point to !
var pos = group.getClientRect();
RotatePoint(group, {x: pos.x + pos.width/2, y: pos.y + pos.height/2});
// Everything is ready so draw the canvas objects set up so far.
s1.draw()
$('#st').on('click', function(){
group.scaleX(1);
var txt = $('#theText').val();
setValue(txt);
})
// set the offset for rotation to the given location and re-position the shape
function RotatePoint(shape, pos){ // where pos = {x: xpos, y: yPos}
var initialPos = shape.getAbsolutePosition();
var moveBy = {x: pos.x - initialPos.x, y: pos.y - initialPos.y};
// offset is relative to initial x,y of shape, so deduct x,y.
shape.offsetX(moveBy.x);
shape.offsetY(moveBy.y);
shape.x(initialPos.x + moveBy.x);
shape.y(initialPos.y + moveBy.y);
}
var setValue = function(newText){
// work out scaling to make text fit into the circle
var txt = this.layer.find('#t1')[0];
txt.text(newText).scale({x:1, y: 1})
var txtSize = txt.getClientRect();
var maxW = (cfg.r); // max allowed width of text
var txtScaleW = (txtSize.width > maxW ? ( maxW / txtSize.width) : 1);
var maxH = cfg.r; // max allowed height of text
var txtScaleH = (txtSize.height > maxH ? ( maxH / txtSize.height) : 1);
// finally decide which is the worst case and use that scaling
var txtScale = ( txtScaleW > txtScaleH ? txtScaleH : txtScaleW);
txt.scale({x: txtScale, y: txtScale});
txt.offset({x: txt.getWidth()/2, y: txt.getHeight()/2});
layer.draw()
}
// set initial text & spin !
setValue('BBB');
var anim, pos = 0, frameCnt = 0
if (anim) {anim.stop(); }
anim = new Konva.Animation(function(frame) {
frameCnt = frameCnt + 1;
if (frameCnt % 2 === 0){
pos = pos + .2
var scaleX = Math.sin(pos)
textValue.visible(scaleX < 0 ? false : true);
group.scaleX(scaleX);
if (pos % 360 === 0){ console.log('spin') }
}
}, layer);
anim.start();
div
{
float: left;
margin: 0 5px;
}
p
{
margin: 0 5px 5px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div id='container1' style="width: 300px, height: 200px;"></div>
<div>
<p> <input type='text' id='theText' value='BBB' /> <button id='st'>Change text</button> </p>
</div>
Related
Is it possible in Konva to animate a shape (marker, circle) along a lline/path. I tried to manually calculate positions over time but this is only feasible if the line is straight from A to B, but I'm interested in a bezier curve and multiple path points.
So I wonder if Konva supports this kind of thing or someone could give a direction how to approach this.
As you have identified, the Path object has some handy methods in getLength() to find the overall path length and getPointAtLength() which can then be used to find the (x,y) at any given point along the length.
In case it helps anyone, I built the path data from the output of another snippet from this other question.
var data = [{"x":34,"y":34},{"x":84,"y":64},{"x":141,"y":79},{"x":181.5,"y":78.5},{"x":218,"y":62},{"x":223,"y":40},{"x":240,"y":26},{"x":259.5,"y":25},{"x":271,"y":40},{"x":292.5,"y":53},{"x":311.25,"y":55.5},{"x":330.625,"y":46.75},{"x":332.3125,"y":30.375},{"x":349.15625,"y":10.1875},{"x":374.578125,"y":10.09375},{"x":392,"y":26},{"x":411,"y":36},{"x":444.5,"y":37},{"x":453.875,"y":27.25},{"x":463.25,"y":17.5},{"x":472.9375,"y":10.625},{"x":494.625,"y":15.75},{"x":530,"y":48},{"x":534,"y":88},{"x":540,"y":150},{"x":552,"y":198},{"x":544,"y":227},{"x":522,"y":256},{"x":504.5,"y":263},{"x":471,"y":262},{"x":448,"y":252},{"x":372,"y":214},{"x":290,"y":146},{"x":256,"y":100},{"x":198,"y":104},{"x":182,"y":140},{"x":204,"y":185},{"x":203,"y":201.5},{"x":190,"y":214},{"x":174.5,"y":218},{"x":155,"y":214},{"x":124,"y":222},{"x":113.5,"y":232.5},{"x":95,"y":227},{"x":75.5,"y":211.5},{"x":72,"y":188},{"x":58,"y":136}]
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container1', width: 600, height: 300});
// Add a layer for line
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
// draw a path.
var path = new Konva.Path({
x: 0,
y: 0,
stroke: 'cyan'
});
layer.add(path)
// Load the path points up using M = moveto, L = lineto.
var p = "M" + data[0].x + " " + data[0].y;
for (var i = 1; i < data.length; i = i + 1){
p = p + " L" + data[i].x + " " + data[i].y;
}
path.setData(p);
// add a circle to be animated along the path
var circle = new Konva.Circle({ x: data[0].x, y: data[0].y, radius: 10, fill: 'Magenta'});
layer.add(circle);
stage.draw();
$('#reset').on('click', function(){
// Now animate a circle along the path
var steps = 50; // number of steps in animation
var pathLen = path.getLength();
var step = pathLen / steps;
var frameCnt = 0, pos =0, pt;
anim = new Konva.Animation(function(frame) {
pos = pos + 1;
pt = path.getPointAtLength(pos * step);
circle.position({x: pt.x, y: pt.y});
}, layer);
anim.start();
})
$('#reset').trigger('click');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<button style='position: absolute; z-index: 10;' id='reset'>Go</button>
<div id='container1' style="width: 300px, height: 200px; background-color: silver;"></div>
<div id='img'></div>
I want to give my users an easy way to visually trace a route on a map or a picture. The solution must let the users add control points that they can use to put bends into the route.
It should work with html5 canvas - I currently use the Konvajs library so a solution that uses this would be good.
In the interests of sharing & learning, if you can suggest solutions using other HTML5 canvas libraries that would be good to see too.
Note: This is not the original question posed. However it emerged over time that this was the actual requirement. The OP asked for means to find an arbitrary point part way along a line / curve in an HTML5 canvas so that a draggable control point could be added at that point to edit the line / curve. The accepted answer does not meet this need. However, an answer to this original question would involve serious collision-detection math and potentially use of bezier control points - in other words it would be a big ask whilst the accepted answer is a very approachable solution with consistent UX.
The original question can be seen in via the edit links below this question.
How about this idea. You click where you want the next point and the route line extends with new positioning handles along the line segments. If you need arrows you can extend the objects herein as you require. You can easily change colours, stroke width, circle opacity etc with attributes of the route class. The points are available in an array and in the standard Konva.js line points list. The JS is vanilla, no other libraries needed or used.
The Export button shows how to grab the (x,y) fixed point objects for export purposes.
Example video here, working code in below snippet.
// Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container1', width: 600, height: 300});
// Add a layer for line
var lineLayer = new Konva.Layer({draggable: false});
s1.add(lineLayer);
// Add a layer for drag points
var pointLayer = new Konva.Layer({draggable: false});
s1.add(pointLayer);
// Add a rectangle to layer to catch events. Make it semi-transparent
var r = new Konva.Rect({x:0, y: 0, width: 600, height: 300, fill: 'black', opacity: 0.1})
pointLayer.add(r)
// Everything is ready so draw the canvas objects set up so far.
s1.draw()
// generic canvas end
// Class for the draggable point
// Params: route = the parent object, opts = position info, doPush = should we just make it or make it AND store it
var DragPoint = function(route, opts, doPush){
var route = route;
this.x = opts.x;
this.y = opts.y;
this.fixed = opts.fixed;
this.id = randId(); // random id.
if (doPush){ // in some cases we want to create the pt then insert it in the run of the array and not always at the end
route.pts.push(this);
}
// random id generator
function randId() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
}
// mark the pt as fixed - important state, shown by filled point
this.makeFixed = function(){
this.fixed = true;
s1.find('#' + this.id)
.fill(route.fillColor);
}
this.kill = function(){
s1.find('#' + this.id)
.remove();
}
this.draw = function(){
// Add point & pt
var circleId = this.id;
var pt = new Konva.Circle({
id: circleId,
x: this.x,
y: this.y,
radius: route.pointRadius,
opacity: route.pointOpacity,
strokeWidth: 2,
stroke: route.strokeColor,
fill: 'transparent',
draggable: 'true'
})
pt.on('dragstart', function(){
route.drawState = 'dragging';
})
pt.on('dragmove', function(){
var pos = this.getPosition();
route.updatePt(this.id(), pos)
route.calc(this.id());
route.draw();
})
pt.on('dragend', function(){
route.drawState = 'drawing';
var pos = this.getPosition();
route.updatePt(this.getId(), pos);
route.splitPts(this.getId());
route.draw();
})
if (this.fixed){
this.makeFixed();
}
route.ptLayer.add(pt);
route.draw();
}
}
var Route = function() {
this.lineLayer = null;
this.ptLayer = null;
this.drawState = '';
this.fillColor = 'Gold';
this.strokeColor = 'Gold';
this.pointOpacity = 0.5;
this.pointRadius = 10;
this.color = 'LimeGreen';
this.width = 5;
this.pts = []; // array of dragging points.
this.startPt = null;
this.endPt = null;
// reset the points
this.reset = function(){
for (var i = 0; i < this.pts.length; i = i + 1){
this.pts[i].kill();
}
this.pts.length = 0;
this.draw();
}
// Add a point to the route.
this.addPt = function(pos, isFixed){
if (this.drawState === 'dragging'){ // do not add a new point because we were just dragging another
return null;
}
this.startPt = this.startPt || pos;
this.endPt = pos;
// create this new pt
var pt = new DragPoint(this, {x: this.endPt.x, y: this.endPt.y, fixed: isFixed}, true, "A");
pt.draw();
pt.makeFixed(); // always fixed for manual points
// if first point ignore the splitter process
if (this.pts.length > 0){
this.splitPts(pt.id, true);
}
this.startPt = this.endPt; // remember the last point
this.calc(); // calculate the line points from the array
this.draw(); // draw the line
}
// Position the points.
this.calc = function (draggingId){
draggingId = (typeof draggingId === 'undefined' ? '---' : draggingId); // when dragging an unfilled point we have to override its automatic positioning.
for (var i = 1; i < this.pts.length - 1; i = i + 1){
var d2 = this.pts[i];
if (!d2.fixed && d2.id !== draggingId){ // points that have been split are fixed, points that have not been split are repositioned mid way along their line segment.
var d1 = this.pts[i - 1];
var d3 = this.pts[i + 1];
var pos = this.getHalfwayPt(d1, d3);
d2.x = pos.x;
d2.y = pos.y;
}
s1.find('#' + d2.id).position({x: d2.x, y: d2.y}); // tell the shape where to go
}
}
// draw the line
this.draw = function (){
if (this.drawingLine){
this.drawingLine.remove();
}
this.drawingLine = this.newLine(); // initial line point
for (var i = 0; i < this.pts.length; i = i + 1){
this.drawingLine.points(this.drawingLine.points().concat([this.pts[i].x, this.pts[i].y]))
}
this.ptLayer.draw();
this.lineLayer.draw();
}
// When dragging we need to update the position of the point
this.updatePt = function(id, pos){
for (var i = 0; i < this.pts.length; i = i + 1){
if (this.pts[i].id === id){
this.pts[i].x = pos.x;
this.pts[i].y = pos.y;
break;
}
}
}
// Function to add and return a line object. We will extend this line to give the appearance of drawing.
this.newLine = function(){
var line = new Konva.Line({
stroke: this.color,
strokeWidth: this.width,
lineCap: 'round',
lineJoin: 'round',
tension : .1
});
this.lineLayer.add(line)
return line;
}
// make pts either side of the split
this.splitPts = function(id, force){
var idx = -1;
// find the pt in the array
for (var i = 0; i < this.pts.length; i = i + 1){
if (this.pts[i].id === id){
idx = i;
if (this.pts[i].fixed && !force){
return null; // we only split once.
}
//break;
}
}
// If idx is -1 we did not find the pt id !
if ( idx === -1){
return null
}
else if (idx === 0 ) {
return null
}
else { // pt not = 0 or max
// We are now going to insert a new pt either side of the one we just dragged
var d1 = this.pts[idx - 1]; // previous pt to the dragged pt
var d2 = this.pts[idx ]; // the pt pt
var d3 = this.pts[idx + 1]; // the next pt after the dragged pt
d2.makeFixed()// flag this pt as no longer splittable
// get point midway from prev pt and dragged pt
var pos = this.getHalfwayPt(d1, d2);
var pt = new DragPoint(this, {x: pos.x, y: pos.y, foxed: false}, false, "C");
pt.draw();
this.pts.splice(idx, 0, pt);
if (d3){
// get point midway from dragged pt to next
pos = this.getHalfwayPt(d2, d3);
var pt = new DragPoint(this, {x: pos.x, y: pos.y, foxed: false}, false, "D");
pt.draw();
this.pts.splice(idx + 2, 0, pt); // note idx + 2 !
}
}
}
// convert last point array entry to handy x,y object.
this.getPoint = function(pts){
return {x: pts[pts.length - 2], y: pts[pts.length - 1]};
}
this.getHalfwayPt = function(d1, d2){
var pos = {
x: d1.x + (d2.x - d1.x)/2,
y: d1.y + (d2.y - d1.y)/2
}
return pos;
}
this.exportPoints = function(){
var list = [], pt;
console.log('pts=' + this.pts.length)
for (var i = 0; i < this.pts.length; i = i + 1){
pt = this.pts[i]
if (pt.fixed){
console.log('push ' + i)
list.push({x: pt.x, y: pt.y});
}
}
return list;
}
}
var route = new Route();
route.lineLayer = lineLayer;
route.ptLayer = pointLayer;
route.fillColor = 'AliceBlue';
route.strokeColor = 'Red';
route.pointOpacity = 0.5;
route.pointRadius = 7;
route.color = '#2982E8'
// Listen for mouse up on the stage to know when to draw points
s1.on('mouseup touchend', function () {
route.addPt(s1.getPointerPosition(), true);
});
// jquery is used here simply as a quick means to make the buttons work.
// Controls for points export
$('#export').on('click', function(){
if ($(this).html() === "Hide"){
$(this).html('Export');
$('#points').hide();
}
else {
$(this).html('Hide');
$('#points')
.css('display', 'block')
.val(JSON.stringify(route.exportPoints()));
}
})
// reset button
$('#reset').on('click', function(){
route.reset();
})
p
{
padding: 4px;
}
#container1
{
background-image: url('https://i.stack.imgur.com/gADDJ.png');
}
#ctrl
{
position: absolute;
z-index: 10;
margin: 0px;
border: 1px solid red;
}
#points
{
width: 500px;
height: 100px;
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.5/konva.min.js"></script>
<p>Click to add a point, click to add another, drag a point to make a bend, etc.
</p>
<div id='ctrl'>
<button id='reset'>Reset</button>
<button id='export'>Export</button>
<textarea id='points'></textarea>
</div>
<div id='container1' style="display: inline-block; width: 300px, height: 200px; background-color: silver; overflow: hidden; position: relative;"></div>
<div id='img'></div>
I would like to draw a line binding a entity to its label with an offset.
CesiumJS allows to offset the label, however its not possible to draw a line (or polyline) from a position to an offset like the red line in this image.
How can i do it? any sugestion?
i'm using pixel offset. but there is no problem to use eye offset
labels.add({
position: Cesium.Cartesian3.fromDegrees(-75.1641667, 29.9522222),
text: 'Another label',
pixelOffset: new Cesium.Cartesian2(100,-100)
});
The best way to do this is probably a billboard with an image of the line on it. The length will never change if it's a pixelOffset. You can put an image of a white line, and use the color property to set any other color.
var viewer = new Cesium.Viewer('cesiumContainer', {
navigationInstructionsInitiallyVisible: false, animation: false, timeline: false,
// These next 6 lines are just to avoid Stack Snippet error messages.
imageryProvider : new Cesium.TileMapServiceImageryProvider({
url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),
}),
baseLayerPicker : false,
geocoder : false,
infoBox : false
});
var scene = viewer.scene;
var offsetX = 50, offsetY = -80;
var pos = Cesium.Cartesian3.fromDegrees(-75.1641667, 29.9522222);
var labels = scene.primitives.add(new Cesium.LabelCollection());
labels.add({
position: pos,
text: 'Another label',
pixelOffset: new Cesium.Cartesian2(offsetX, offsetY)
});
var canvas = document.createElement('canvas');
canvas.width = Math.abs(offsetX);
canvas.height = Math.abs(offsetY);
var context2D = canvas.getContext('2d');
context2D.beginPath();
context2D.lineWidth = 3;
context2D.strokeStyle = '#ffffff';
context2D.moveTo((offsetX < 0) ? -offsetX : 0, (offsetY < 0) ? -offsetY : 0);
context2D.lineTo((offsetX < 0) ? 0 : offsetX, (offsetY < 0) ? 0 : offsetY);
context2D.stroke();
var billboards = scene.primitives.add(new Cesium.BillboardCollection());
var billboard = billboards.add({
color : Cesium.Color.RED,
image : canvas,
pixelOffset: new Cesium.Cartesian2(offsetX * 0.5, offsetY * 0.5),
position : pos
});
html, body, #cesiumContainer {
width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
}
<link href="https://cesium.com/downloads/cesiumjs/releases/1.78/Build/Cesium/Widgets/widgets.css" rel="stylesheet"/>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.78/Build/Cesium/Cesium.js"></script>
<div id="cesiumContainer"></div>
I have problem with resume animation after stop it. Any suggestion ? I using kineticjs to make element run along the path, after i reach the end, the animation stop for 2 second then start again.
here my code :
var stage = new Kinetic.Stage({
container: 'canvas',
height: 484,
width: 478
});
var layer = new Kinetic.Layer();
stage.add(layer);
var img = new Image();
img.src = 'images/ani/bullet_blue.png';
var circle = new Image();
circle.src = 'images/ani/1.png';
var shapeCircle = new Kinetic.Image({
x: 10,
y: 10,
image: circle,
width: circle.width,
height: circle.height,
offset: {
x: 0,
y: 0
}
});
layer.add(shapeCircle);
layer.draw();
function animation(points, shape, duration, loop, callback) {
layer.add(shape);
window.anim = new Kinetic.Animation(function (frame) {
var time = frame.time, timeDiff = frame.timeDiff, frameRate = frame.frameRate;
var percent = time / duration, scale = 0.5, opacity = 0;
if (percent < 0.05 || percent > 0.95) opacity = 0;
else opacity = 1;
// scale calculate
if (percent < 0.5) {
scale += percent
}
else if (percent > 0.5) {
scale = 1 - (percent - 0.5)
}
if (percent > 1) {
anim.stop();
percent = 0;
setTimeout(function () { anim.start(); }, 500);
} else {
var pos = Math.ceil(percent * points.length);
pos = pos > (points.length - 1) ? (points.length - 1) : pos;
if (pos == points.length - 1) anim.stop();
shape.setScale(scale, scale);
shape.setOpacity(opacity);
shape.setPosition(points[pos].x, points[pos].y);
}
}, layer);
anim.start();
}
animation(points1, new Kinetic.Image({
x: points1[0].x,
y: points1[0].y,
image: img,
width: img.width,
height: img.height,
opacity: 0,
scaleX: 0.5,
scaleY: 0.5,
offset: {
x: 15,
y: 30
}
}), 2000);
If you want a repeating animation, you might consider a tween instead.
When a tween finishes, it fires a finished event. In that finished handler, you can:
Reset the tween to the starting position using tween.reset,
Start a timer to wait 2 seconds using setTimer,
When the timer fires, restart the tween using tween.start.
I am trying to calculate the angle of rotation of a circle, I am using the following script:
var circle = new Kinetic.Circle({
x: 256,
y: 256,
radius: 140,
stroke: 'black',
strokeWidth: 4 ,
offset: [0, 0],
draggable: true,
dragBoundFunc: function (pos) {
var pos = stage.getMousePosition();
var xd = 140 - pos.x;
var yd = 140 - pos.y;
var theta = Math.atan2(yd, xd);
var degree = (theta / (Math.PI / 180) - 45);
this.setRotationDeg(degree);
return {
x: this.getAbsolutePosition().x,
y: this.getAbsolutePosition().y
};
}
});
I don't think it is accurate, I added a shape inside the circle to see the rotation but could not group them together, I would appreciate your suggestions on how to calculate the degree of rotation and how to group the shape with the circle so the rotate at the same time. The complete project script is at http://jsfiddle.net/user373721/Ja6GB. Thanks in advance.
Here is how you calculate the angle of the mouse position from "12 o'clock"
Pretend your canvas is a clock centered in the canvas.
Here's how to calculate the angle of the current mouse position assuming 12 o'clock is zero degrees.
function degreesFromTwelveOclock(cx,cy,mouseX,mouseY){
// calculate the angle(theta)
var theta=Math.atan2(mouseY-centerY,mouseX-centerX);
// be sure theta is positive
if(theta<0){theta += 2*Math.PI};
// convert to degrees and rotate so 0 degrees = 12 o'clock
var degrees=(theta*180/Math.PI+90)%360;
return(degrees);
}
Here is complete code and a Fiddle: http://jsfiddle.net/m1erickson/HKq77/
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var centerX=canvas.width/2;
var centerY=canvas.height/2;
var radius=10;
// draw a center dot for user's reference
ctx.beginPath();
ctx.arc(centerX,centerY, radius, 0 , 2 * Math.PI, false);
ctx.fill();
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
$("#movelog").html("Mouse: "+ mouseX + " / " + mouseY);
$("#angle").html("Angle: "+parseInt(degreesFromTwelveOclock(centerX,centerY,mouseX,mouseY)));
}
function degreesFromTwelveOclock(cx,cy,mouseX,mouseY){
// calculate the angle(theta)
var theta=Math.atan2(mouseY-centerY,mouseX-centerX);
// be sure theta is positive
if(theta<0){theta += 2*Math.PI};
// convert to degrees and rotate so 0 degrees = 12 o'clock
var degrees=(theta*180/Math.PI+90)%360;
return(degrees);
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<p id="movelog">Move</p>
<p id="angle">Out</p>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>