I am using fabricJs library to create rectangles and want to get mouse position after zooming
I use this code to start drawing the rectangle, in scale = 1, it works right but after i get zoom in for example and click i get the rectangle start point in the point not i click on, what cause this ?
// Create new rectangle
canvas.on('mouse:down', function (options) {
if (canvas.getActiveObject()) {
return false;
}
started = true;
ex = (posx);
ey = (posy);
var colors = ['#FF8080', '#D5D5E6', '#C0F2C0', '#8080E4', '#CCCCAA'];
var rectangle_color = colors[Math.floor(Math.random() * colors.length)];
var square = new fabric.Rect({
width: 0,
height: 0,
left: ex,
top: ey,
fill: rectangle_color
});
var square = new fabric.Rect({ width: 0, height: 0, left: ex, top: ey, fill: rectangle_color });
canvas.add(square);
canvas.setActiveObject(square);
});
You need your real coordinates of your mouse not some scope declared posx and posy:
canvas.on('mouse:down', function (event) {
if (canvas.getActiveObject()) {
return false;
}
var pointer = canvas.getPointer(event.e);
var posx = pointer.x;
var posy = pointer.y;
//...
//your code
//...
}
Related
I can zoom-in and zoom-out using a computer inside the canvas. However, I cannot zoom-in and zoom-out on mobile devices (smartphone, tablet, etc.). When I looked at the documentation, I found the "touch: gesture" method, but I could not adapt it to my code and execute it. How can I do that? Below I add the code I use.
var canvas = new fabric.Canvas('c');
canvas.setBackgroundImage('https://i.hizliresim.com/iBHC0t.jpg', canvas.renderAll.bind(canvas));
canvas.selection = false;
//var uniqid = "0";
var uniqids = 0;
$("#door").on("click", function(e) {
rect = new fabric.Rect({
id:uniqid,
left: 40,
top: 40,
width: 35,
height: 50,
fill: 'blue',
stroke: 'blue',
strokeWidth: 5,
strokeUniform: false,
hasControls : true,
});
var uniqid = uniqids.toString();
var text = new fabric.Text(uniqid, {
fontSize: 30,
originX: 'center',
originY: 'right'
});
var group = new fabric.Group([ rect, text ], {
left: 0,
top: 100,
});
canvas.add(group);
canvas.add(rect);
uniqids++;
});
//*****************************
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var zoom = canvas.getZoom();
zoom *= 0.999 ** delta;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
canvas.setZoom(zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
})
//***************************************
$("#save").on("click", function(e) {
$(".save").html(canvas.toSVG());
});
$('#delete').click(function() {
var activeObject = canvas.getActiveObjects();
canvas.discardActiveObject();
canvas.remove(...activeObject);
});
$("#btnResetZoom").on("click", function(e) {
canvas.setViewportTransform([1,0,0,1,0,0]);
});
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var zoom = canvas.getZoom();
zoom *= 0.999 ** delta;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
var shiftKeyDown = true;
var mouseDownPoint = null;
canvas.on('mouse:move', function(options) {
if (shiftKeyDown && mouseDownPoint) {
var pointer = canvas.getPointer(options.e, true);
var mouseMovePoint = new fabric.Point(pointer.x, pointer.y);
canvas.relativePan(mouseMovePoint.subtract(mouseDownPoint));
mouseDownPoint = mouseMovePoint;
keepPositionInBounds(canvas);
}
});
/*
canvas.on('mouse:over', function(e) {
e.target.set('fill', 'red');
canvas.renderAll();
});
*/
#c {
background-color: grey;
margin-top: 10px;
}
button {
padding: 10px 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.1.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<button id="door">Door</button>
<button id="delete">Delete Door</button>
<button id="save">Save</button>
<button id="btnResetZoom">Reset Zoom</i></button>
<p>Save bastıktan sonra altta SVG dosyası oluşur</p>
<br>
<canvas id="c" width="800" height="800"></canvas>
<br>
<p class="save">
</p>
Thank you for your question. I encountered the same issue once and I figured it out very easily.
However, I cannot zoom-in and zoom-out on mobile devices (smartphone, tablet, etc.).
Did you run Demo for touch events on your smartphone or tablet?
Then as you can see, there's no need to add some custom code for the pinch zoom or rotate. It automatically enables you to use those features.
The main point is, you should use the custom version of Fabric with touch events enabled.
It is just fabric_with_gestures.js. I also used prism.js together.
I am using fabric js v4.1.0 and I would like to show the blue select boundary of the selected object outside the canvas boundary if it is partially dragged out of canvas and have the scaling and rotate functions also in this as the object which is selected inside the canvas.
Here is a demo of my current code https://codepen.io/raja-basumatary/pen/qBZGobQ
function isCanvasSupported() {
var elem = document.createElement("canvas");
return !!(elem.getContext && elem.getContext("2d"));
}
if (!isCanvasSupported()) {
document.getElementById("nocanvas").className = "";
document.getElementById("myCanvas").className = "hidden";
} else {
// Constants
var canvasHeight = 580;
var canvasWidth = 865;
var canvas = new fabric.Canvas("myCanvas");
canvas.setHeight(canvasHeight);
canvas.setWidth(canvasWidth);
var rect = new fabric.Rect({
left: 150,
top: 100,
fill: "#eef",
width: 150,
height: 250
});
canvas.add(rect);
}
I want something like this
image
I am quite new to javascript, and I have been following tutorials at https://konvajs.org/ to help me learn their library.
I am currently trying to make it so you can load and select local images to move, resize, and rotate them.
This is the code I have so far:
<head>
<!-- USE DEVELOPMENT VERSION -->
<script src="https://unpkg.com/konva#7.0.0/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Select and Transform Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/3.2.5/konva.min.js"></script>
<div>Render a local image without upload</div>
<div>
<input type="file" id="file_input">
</div>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
var layer = new Konva.Layer();
stage.add(layer);
//add images
// listen for the file input change event and load the image.
$("#file_input").change(function(e){
var URL = window.webkitURL || window.URL;
var url = URL.createObjectURL(e.target.files[0]);
var img = new Image();
img.src = url;
img.onload = function() {
var img_width = img.width;
var img_height = img.height;
// calculate dimensions to get max 300px
var max = 300;
var ratio = (img_width > img_height ? (img_width / max) : (img_height / max))
// now load the Konva image
var theImg = new Konva.Image({
image: img,
x: 50,
y: 30,
width: img_width/ratio,
height: img_height/ratio,
draggable: true,
rotation: 0
});
layer.add(theImg);
layer.draw();
}
});
var tr = new Konva.Transformer();
layer.add(tr);
// by default select all shapes
// at this point basic demo is finished!!
// we just have several transforming nodes
layer.draw();
// add a new feature, lets add ability to draw selection rectangle
var selectionRectangle = new Konva.Rect({
fill: 'rgba(0,0,255,0.5)',
});
layer.add(selectionRectangle);
var x1, y1, x2, y2;
stage.on('mousedown touchstart', (e) => {
// do nothing if we mousedown on eny shape
if (e.target !== stage) {
return;
}
x1 = stage.getPointerPosition().x;
y1 = stage.getPointerPosition().y;
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
layer.draw();
});
stage.on('mousemove touchmove', () => {
// no nothing if we didn't start selection
if (!selectionRectangle.visible()) {
return;
}
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;
selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
});
layer.batchDraw();
});
stage.on('mouseup touchend', () => {
// no nothing if we didn't start selection
if (!selectionRectangle.visible()) {
return;
}
// update visibility in timeout, so we can check it in click event
setTimeout(() => {
selectionRectangle.visible(false);
layer.batchDraw();
});
var shapes = stage.find('.rect').toArray();
var box = selectionRectangle.getClientRect();
var selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect())
);
tr.nodes(selected);
layer.batchDraw();
});
// clicks should select/deselect shapes
stage.on('click tap', function (e) {
// if we are selecting with rect, do nothing
if (selectionRectangle.visible()) {
return;
}
// if click on empty area - remove all selections
if (e.target === stage) {
tr.nodes([]);
layer.draw();
return;
}
// do nothing if clicked NOT on our rectangles
if (!e.target.hasName('rect')) {
return;
}
// do we pressed shift or ctrl?
const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
const isSelected = tr.nodes().indexOf(e.target) >= 0;
if (!metaPressed && !isSelected) {
// if no key pressed and the node is not selected
// select just one
tr.nodes([e.target]);
} else if (metaPressed && isSelected) {
// if we pressed keys and node was selected
// we need to remove it from selection:
const nodes = tr.nodes().slice(); // use slice to have new copy of array
// remove node from array
nodes.splice(nodes.indexOf(e.target), 1);
tr.nodes(nodes);
} else if (metaPressed && !isSelected) {
// add the node into selection
const nodes = tr.nodes().concat([e.target]);
tr.nodes(nodes);
}
layer.draw();
});
</script>
Demo: https://www.w3schools.com/code/tryit.asp?filename=GG5QCXFMLFXJ
I am unsure how to make the selecting work on images, rather than rectangles.
Thank you
In the demo, you will see that "rect" names is used to detect selectable shapes. That is a useful approach, because you may have non-selectable shapes on the stage (like a background).
So to check a shape if it is selectable, we use:
e.target.hasName('rect')
So just remember to add "rect" name to new images on the stage:
// now load the Konva image
var theImg = new Konva.Image({
name: 'rect',
image: img,
x: 50,
y: 30,
width: img_width/ratio,
height: img_height/ratio,
draggable: true,
rotation: 0
});
The name itself is not very important. You can find a different way to detect "selectable" nodes.
https://www.w3schools.com/code/tryit.asp?filename=GG6XVLB39IKW
Fabric JS question.
SCREENSHOT this is what my Fabric JS app looks like
CODEPEN https://codepen.io/zaynbuksh/pen/VVKRxj?editors=0011 (alt-click-drag to pan, scroll-wheel to zoom)
TLDR; How do I get the line to stick after panning and zooming a few times?
I am developing a Fabric JS app for labeling images of specimens. As part of this, people want to be able to zoom in on what each label is pointing at. I have been asked to make the labels remain visible when the specimen image is zoomed in. From research, people recommend two canvases stacked on top of each other.
I have created two Fabric JS canvas instances, layered on top of each other. The canvas at the bottom holds a background image that can be zoomed and panned, the canvas above it shows a pointer-line/label that is not zoomed (to keep the label visible at all times).
At first everything works - the line stays in sync with the image when I pan and zoom the first time only. I get problems with syncing the line to the image after that.
The problem manifests when I pan then zoom two or more times. The problem repeats each time I pan and then zoom i.e. the line moves when I zoom, but then stays in sync when I pan, moves again when I zoom again, pans normally and so on...
(Pan is handled by alt-click-drag, Zoom is handled by scroll wheel)
/*
"mouse:wheel" event is where zooms are handled
"mouse:move" event is where panning is handled
*/
// create Fabric JS canvas'
var labelsCanvas = new fabric.Canvas("labelsCanvas");
var specimenCanvas = new fabric.Canvas("specimenCanvas");
//set defaults
var startingPositionForLine = 100;
const noZoom = 1;
var wasPanned = false;
var panY2 = startingPositionForLine;
var panX2 = startingPositionForLine;
var zoomY2 = startingPositionForLine;
var zoomX2 = startingPositionForLine;
// set starting zoom for specimen canvas
var specimenZoom = noZoom;
/*
Add pointer, label and background image into canvas
*/
// create a pointer line
var line = new fabric.Line([150, 35, panX2, panY2], {
fill: "red",
stroke: "red",
strokeWidth: 3,
strokeDashArray: [5, 2],
// selectable: false,
evented: false
});
// create text label
var text = new fabric.Text("Label 1", {
left: 100,
top: 0,
// selectable: false,
evented: false,
backgroundColor: "red"
});
// add both into "Labels" canvas
labelsCanvas.add(text);
labelsCanvas.add(line);
// add a background image into Specimen canvas
fabric.Image.fromURL(
"https://upload.wikimedia.org/wikipedia/commons/c/cb/Skull_brain_human_normal.svg",
function(oImg) {
oImg.left = 0;
oImg.top = 0;
oImg.scaleToWidth(300);
oImg.scaleToHeight(300);
specimenCanvas.add(oImg);
}
);
/*
Handle mouse events
*/
// zoom the specimen image canvas via a mouse scroll-wheel event
labelsCanvas.on("mouse:wheel", function(opt) {
// scroll value e.g. 5, 6 -1, -18
var delta = opt.e.deltaY;
// zoom level in specimen
var zoom = specimenCanvas.getZoom();
console.log("zoom ", zoom);
// make zoom smaller
zoom = zoom + delta / 200;
// use sane defaults for zoom
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
// create new zoom value
zoomX2 = panX2 * zoom;
zoomY2 = panY2 * zoom;
// save the zoom
specimenZoom = zoom;
// set the specimen canvas zoom
specimenCanvas.setZoom(zoom);
// move line to sync it with the zoomed image
line.set({
x2: zoomX2,
y2: zoomY2
});
console.log("zoomed line ", line.x2);
// render the changes
this.requestRenderAll();
// block default mouse behaviour
opt.e.preventDefault();
opt.e.stopPropagation();
console.log(labelsCanvas.viewportTransform[4]);
// stuff I've tried to fix errors
line.setCoords();
specimenCanvas.calcOffset();
});
// pan the canvas
labelsCanvas.on("mouse:move", function(opt) {
if (this.isDragging) {
// pick up the click and drag event
var e = opt.e;
// sync the label position with the panning
text.left = text.left + (e.clientX - this.lastPosX);
var x2ToUse;
var y2ToUse;
// UNZOOMED canvas is being panned
if (specimenZoom === noZoom) {
x2ToUse = panX2;
y2ToUse = panY2;
// move the image using the difference between
// the current position and last known position
line.set({
x1: line.x1 + (e.clientX - this.lastPosX),
y1: line.y1,
x2: x2ToUse + (e.clientX - this.lastPosX),
y2: y2ToUse + (e.clientY - this.lastPosY)
});
// set the new panning value
panX2 = line.x2;
panY2 = line.y2;
// stuff I've tried
// zoomX2 = line.x2;
// zoomY2 = line.y2;
}
// ZOOMED canvas is being panned
else
{
x2ToUse = zoomX2;
y2ToUse = zoomY2;
// stuff I've tried
// x2ToUse = panX2;
// y2ToUse = panY2;
// move the image using the difference between
// the current position and last known ZOOMED position
line.set({
x1: line.x1 + (e.clientX - this.lastPosX),
y1: line.y1,
x2: x2ToUse + (e.clientX - this.lastPosX),
y2: y2ToUse + (e.clientY - this.lastPosY)
});
zoomX2 = line.x2;
zoomY2 = line.y2;
}
// hide label/pointer when it is out of view
if (text.left < 0 || line.y2 < 35) {
text.animate("opacity", "0", {
duration: 15,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
line.animate("opacity", "0", {
duration: 15,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
}
// show label/pointer when it is in view
else
{
text.animate("opacity", "1", {
duration: 25,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
line.animate("opacity", "1", {
duration: 25,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
}
specimenCanvas.viewportTransform[4] += e.clientX - this.lastPosX;
specimenCanvas.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
specimenCanvas.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
console.log(line.x2);
wasPanned = true;
});
labelsCanvas.on("mouse:down", function(opt) {
var evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
});
labelsCanvas.on("mouse:up", function(opt) {
this.isDragging = false;
this.selection = true;
});
.canvas-container {
position: absolute!important;
left: 0!important;
top: 0!important;
}
.canvas {
position: absolute;
top: 0;
right: 0;
border: solid red 1px;
}
.label-canvas {
z-index: 2;
}
.specimen-canvas {
z-index: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
<h1>
Dual canvas test
</h1>
<div style="position: relative; height: 300px">
<canvas class="canvas specimen-canvas" id="specimenCanvas" width="300" height="300"></canvas>
<canvas class="canvas label-canvas" id="labelsCanvas" width="300" height="300"></canvas>
</div>
As a side note, I think you're overcomplicating things a little bit. You don't really need to store panX/panY and zoomX/zoomY (zoomed pan as I've guessed) separately, they're already there in your line's coordinates. Just saying, because they've probably contributed to the confusion/debugging. The core idea of a fix, however, is that you should multiply your line's coordinates not by the whole zoom value but by the newZoom / previousZoom ratio. I've updated your snippet, it seems to work as expected:
/*
"mouse:wheel" event is where zooms are handled
"mouse:move" event is where panning is handled
*/
// create Fabric JS canvas'
var labelsCanvas = new fabric.Canvas("labelsCanvas");
var specimenCanvas = new fabric.Canvas("specimenCanvas");
//set defaults
var startingPositionForLine = 100;
const noZoom = 1;
var wasPanned = false;
var panY2 = startingPositionForLine;
var panX2 = startingPositionForLine;
var zoomY2 = startingPositionForLine;
var zoomX2 = startingPositionForLine;
// set starting zoom for specimen canvas
var specimenZoom = noZoom;
var prevZoom = noZoom;
/*
Add pointer, label and background image into canvas
*/
// create a pointer line
var line = new fabric.Line([150, 35, panX2, panY2], {
fill: "red",
stroke: "red",
strokeWidth: 3,
strokeDashArray: [5, 2],
// selectable: false,
evented: false
});
// create text label
var text = new fabric.Text("Label 1", {
left: 100,
top: 0,
// selectable: false,
evented: false,
backgroundColor: "red"
});
// add both into "Labels" canvas
labelsCanvas.add(text);
labelsCanvas.add(line);
// add a background image into Specimen canvas
fabric.Image.fromURL(
"https://upload.wikimedia.org/wikipedia/commons/c/cb/Skull_brain_human_normal.svg",
function(oImg) {
oImg.left = 0;
oImg.top = 0;
oImg.scaleToWidth(300);
oImg.scaleToHeight(300);
specimenCanvas.add(oImg);
}
);
window.specimenCanvas = specimenCanvas
/*
Handle mouse events
*/
// zoom the specimen image canvas via a mouse scroll-wheel event
labelsCanvas.on("mouse:wheel", function(opt) {
// scroll value e.g. 5, 6 -1, -18
var delta = opt.e.deltaY;
// zoom level in specimen
var zoom = specimenCanvas.getZoom();
var lastZoom = zoom
// make zoom smaller
zoom = zoom + delta / 200;
// use sane defaults for zoom
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
// save the zoom
specimenZoom = zoom;
// set the specimen canvas zoom
specimenCanvas.setZoom(zoom);
// move line to sync it with the zoomed image
var zoomRatio = zoom / lastZoom
console.log('zoom ratio: ', zoomRatio)
line.set({
x2: line.x2 * zoomRatio,
y2: line.y2 * zoomRatio
});
// console.log("zoomed line ", line.x2);
// render the changes
this.requestRenderAll();
// block default mouse behaviour
opt.e.preventDefault();
opt.e.stopPropagation();
// console.log(labelsCanvas.viewportTransform[4]);
// stuff I've tried to fix errors
line.setCoords();
specimenCanvas.calcOffset();
});
// pan the canvas
labelsCanvas.on("mouse:move", function(opt) {
if (this.isDragging) {
// pick up the click and drag event
var e = opt.e;
// sync the label position with the panning
text.left = text.left + (e.clientX - this.lastPosX);
// UNZOOMED canvas is being panned
if (specimenZoom === noZoom) {
x2ToUse = panX2;
y2ToUse = panY2;
// move the image using the difference between
// the current position and last known position
line.set({
x1: line.x1 + (e.clientX - this.lastPosX),
y1: line.y1,
x2: line.x2 + (e.clientX - this.lastPosX),
y2: line.y2 + (e.clientY - this.lastPosY)
});
// stuff I've tried
// zoomX2 = line.x2;
// zoomY2 = line.y2;
}
// ZOOMED canvas is being panned
else
{
// move the image using the difference between
// the current position and last known ZOOMED position
line.set({
x1: line.x1 + (e.clientX - this.lastPosX),
y1: line.y1,
x2: line.x2 + (e.clientX - this.lastPosX),
y2: line.y2 + (e.clientY - this.lastPosY)
});
}
// hide label/pointer when it is out of view
if (text.left < 0 || line.y2 < 35) {
text.animate("opacity", "0", {
duration: 15,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
line.animate("opacity", "0", {
duration: 15,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
}
// show label/pointer when it is in view
else
{
text.animate("opacity", "1", {
duration: 25,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
line.animate("opacity", "1", {
duration: 25,
onChange: labelsCanvas.renderAll.bind(labelsCanvas)
});
}
specimenCanvas.viewportTransform[4] += e.clientX - this.lastPosX;
specimenCanvas.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
specimenCanvas.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
prevZoom = specimenCanvas.getZoom()
}
// console.log(line.x2);
wasPanned = true;
});
labelsCanvas.on("mouse:down", function(opt) {
var evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
});
labelsCanvas.on("mouse:up", function(opt) {
this.isDragging = false;
this.selection = true;
});
.canvas-container {
position: absolute!important;
left: 0!important;
top: 0!important;
}
.canvas {
position: absolute;
top: 0;
right: 0;
border: solid red 1px;
}
.label-canvas {
z-index: 2;
}
.specimen-canvas {
z-index: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
<h1>
Dual canvas test
</h1>
<div style="position: relative; height: 300px">
<canvas class="canvas specimen-canvas" id="specimenCanvas" width="300" height="300"></canvas>
<canvas class="canvas label-canvas" id="labelsCanvas" width="300" height="300"></canvas>
</div>
I need help only having the anchors for rotating. Right now there is five anchors and I don't know how to get rid of all of them except the rotate one. I would also only like the anchors to show when the user hovers over the image
Here is my code
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<body onmousedown="return false;">
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.4.min.js">
</script>
<script>
function update(activeAnchor) {
var group = activeAnchor.getParent();
var topLeft = group.get('.topLeft')[0];
var topRight = group.get('.topRight')[0];
var bottomRight = group.get('.bottomRight')[0];
var bottomLeft = group.get('.bottomLeft')[0];
var rotateAnchor = group.get('.rotateAnchor')[0];
var image = group.get('Image')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
var imageWidth = image.getWidth();
var imageHeight = image.getHeight();
var offsetX = Math.abs((topLeft.getX() + bottomRight.getX() + 10) / 2);
var offsetY = Math.abs((topLeft.getY() + bottomRight.getY() + 10) / 2);
// update anchor positions
switch (activeAnchor.getName()) {
case 'rotateAnchor':
group.setOffset(offsetX, offsetY);
break;
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
break;
case 'bottomRight':
topRight.setX(anchorX);
bottomLeft.setY(anchorY);
break;
case 'bottomLeft':
topLeft.setX(anchorX);
bottomRight.setY(anchorY);
break;
}
rotateAnchor.setX(topRight.getX() + 5);
rotateAnchor.setY(topRight.getY() + 20);
image.setPosition((topLeft.getPosition().x + 20), (topLeft.getPosition().y + 20));
var width = topRight.getX() - topLeft.getX() - 30;
var height = bottomLeft.getY() - topLeft.getY() - 30;
if (width && height) {
image.setSize(width, height);
}
}
function addAnchor(group, x, y, name, dragBound) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
if (dragBound == 'rotate') {
anchor.setAttrs({
dragBoundFunc: function (pos) {
return getRotatingAnchorBounds(pos, group);
}
});
}
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function getRotatingAnchorBounds(pos, group) {
var topLeft = group.get('.topLeft')[0];
var bottomRight = group.get('.bottomRight')[0];
var topRight = group.get('.topRight')[0];
var absCenterX = Math.abs((topLeft.getAbsolutePosition().x + 5 + bottomRight.getAbsolutePosition().x + 5) / 2);
var absCenterY = Math.abs((topLeft.getAbsolutePosition().y + 5 + bottomRight.getAbsolutePosition().y + 5) / 2);
var relCenterX = Math.abs((topLeft.getX() + bottomRight.getX()) / 2);
var relCenterY = Math.abs((topLeft.getY() + bottomRight.getY()) / 2);
var radius = distance(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20);
var scale = radius / distance(pos.x, pos.y, absCenterX, absCenterY);
var realRotation = Math.round(degrees(angle(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20)));
var rotation = Math.round(degrees(angle(absCenterX, absCenterY, pos.x, pos.y)));
rotation -= realRotation;
group.setRotationDeg(rotation);
return {
y: Math.round((pos.y - absCenterY) * scale + absCenterY),
x: Math.round((pos.x - absCenterX) * scale + absCenterX)
};
}
function radians(degrees) { return degrees * (Math.PI / 180); }
function degrees(radians) { return radians * (180 / Math.PI); }
// Calculate the angle between two points.
function angle(cx, cy, px, py) {
var x = cx - px;
var y = cy - py;
return Math.atan2(-y, -x);
}
// Calculate the distance between two points.
function distance(p1x, p1y, p2x, p2y) {
return Math.sqrt(Math.pow((p2x - p1x), 2) + Math.pow((p2y - p1y), 2));
}
function initStage(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 400
});
var darthVaderGroup = new Kinetic.Group({
x: 270,
y: 100,
draggable: true
});
var yodaGroup = new Kinetic.Group({
x: 100,
y: 110,
draggable: true
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(darthVaderGroup);
layer.add(yodaGroup);
stage.add(layer);
// darth vader
var darthVaderImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.darthVader,
width: 200,
height: 138,
name: 'image'
});
darthVaderGroup.add(darthVaderImg);
addAnchor(darthVaderGroup, -20, -20, 'topLeft', 'none');
addAnchor(darthVaderGroup, 220, -20, 'topRight', 'none');
addAnchor(darthVaderGroup, 220, 158, 'bottomRight', 'none');
addAnchor(darthVaderGroup, -20, 158, 'bottomLeft','none');
addAnchor(darthVaderGroup, 225, 0, 'rotateAnchor','rotate');
darthVaderGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg'
};
loadImages(sources, initStage);
</script>
</body>
</html>
You can use each anchors show/hide methods inside the images mouseenter/mouseleave events to display the anchors when the mouse enters the image:
image.on("mouseleave",function(){ anchor1.hide(); }
image.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
Problem is that since your anchors are partly outside your image, so hiding the anchors when the mouse leaves the image might make the anchors "disappear" when the user intends to use them.
The ideal solution would be to listen for mouseenter/mouseleave events on the group which contains the image but also extends to include the outside part of the anchors. Unfortunately, a Kinetic.Group will not respond to mouseenter/mouseleave events.
A workaround is to create a Kinetic.Rect background to the group which includes the images plus the anchors. The rect will listen for mouseenter/mouseleave events and will show/hide the anchors. If you don't want the background rect to be visible, just set it's opacity to .001. The rect will still listen for events, but will be invisible.
groupBackgroundRect.on("mouseleave",function(){ anchor1.hide(); }
groupBackgroundRect.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
A related note:
With KineticJS, combining rotation with resizing is made more difficult than it needs to be because KineticJS uses offsetX/offsetY as both an object's rotation point and as an offset to its position. Your key to making it work will be to re-center the offset point after resizing so that your rotation takes place around the new centerpoint--not the previous centerpoint. (or reset the offset reference point to any other point that you want to rotate around).