JSFiddle: https://jsfiddle.net/bd7kc24a/20/
A coworker and I have hit a snag with our attempt to use canvas and we are looking to have a Rect on the canvas to be the actual work area. We are trying to add a grid and/or dot background to that Rect, but we need that section to not scale when zooming occurs. If there is a simple way to do this, we'd love to know about it, but this is where we are currently.
The example jsfiddle above kinda does what we need it to do. But we are having issues:
Persisting that to the database as well as the redux store.
Making sure the grid stays the same pixel distance instead of zooming with objects
So I'm trying to create a custom subclass to re-apply that static canvas background when serialization occurs, however once I've added this base class it will not longer do the loadFromJSON call or hit the callback.
Any help would be appreciated! Especially if there is a simple way to do this that we simply missed in the documentation.
JSFiddle: https://jsfiddle.net/bd7kc24a/20/
'use strict';
debugger;
fabric.CanvasMap = fabric.util.createClass(fabric.Rect, {
type: 'canvasMap',
initialize: function(element, options) {
this.callSuper('initialize', element, options);
options && this.set('name', options.name);
options && this.set('imageRef', options.imageRef);
this.rescaleBackground(10);
},
rescaleBackground: function(scale) {
const padding = 0;
this.imageRef.scaleToWidth(scale);
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(this.imageRef);
patternSourceCanvas.setDimensions({
width: this.imageRef.getWidth() + padding,
height: this.imageRef.getHeight() + padding
});
let pattern = new fabric.Pattern({
source: patternSourceCanvas.getElement(),
repeat: 'repeat'
});
this.setPatternFill(pattern);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
name: this.name
});
}
});
fabric.CanvasMap.fromObject = function (object, callback) {
var _enlivenedObjects;
object.imageRef = squareImg;
fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
delete object.objects;
_enlivenedObjects = enlivenedObjects;
});
const newCanvasMap = new fabric.CanvasMap(_enlivenedObjects, object);
return newCanvasMap;
};
let floorPlan = new fabric.Canvas('floorPlan', {
hoverCursor: 'pointer',
backgroundColor: '#cccccc'
});
function reloadFromJSON() {
let jsonMap = floorPlan.toJSON();
let jsonMapString = JSON.stringify(jsonMap);
floorPlan.loadFromJSON(jsonMapString, function() {
floorPlan.renderAll();
});
}
//let mapBackground = new fabric.Canvas();
function addShape() {
let coordinates = floorPlan.getVpCenter();
const circle = new fabric.Circle({
name: 'some circle',
radius: 20,
fill: 'blue',
left: coordinates.x - 20,
top: coordinates.y - 20
});
floorPlan.add(circle);
}
let defaultScale = 10;
let squareSVG = '<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><g> <title>background</title><rect fill="#fff" id="canvas_background" height="12" width="12" y="-1" x="-1"/></g><g> <title>Layer 1</title> <rect id="svg_1" height="9.87496" width="9.99996" y="0.062519" x="0.031269" stroke-width="1.5" stroke="#000000" fill="none"/> </g></svg>'
let squareImg;
fabric.loadSVGFromString(squareSVG, function(objects, options) {
squareImg = fabric.util.groupSVGElements(objects, options);
const map = new fabric.CanvasMap({
imageRef: squareImg,
height: 300, width: 300,
left: 20, top: 20,
strokeWidth: 2,
stroke: 'red',
selectable: false,
evented: false, hasBorders: true, hasControls: false
});
floorPlan.add(map);
floorPlan.renderAll();
});
function setBackground(scale) {
floorPlan._objects[0].rescaleBackground(scale);
}
//logic for binding objects to MAP
floorPlan.observe('object:moving', function(e) {
let obj = e.target;
if (obj.getHeight() > map.height || obj.getWidth() > map.width) {
obj.setScaleY(obj.originalState.scaleY);
obj.setScaleX(obj.originalState.scaleX);
}
obj.setCoords();
if (obj.top - (obj.cornerSize / 2) < map.top ||
obj.left - (obj.cornerSize / 2) < map.left) {
console.log(map.top, map.left);
obj.top = Math.max(obj.top, obj.top - obj.top + (obj.cornerSize / 2) + map.top);
obj.left = Math.max(obj.left, obj.left - obj.left + (obj.cornerSize / 2) + map.left);
}
if (obj.top + obj.height + obj.cornerSize > map.height ||
obj.left + obj.width + obj.cornerSize > map.width) {
obj.top = Math.min(obj.top, (map.height + map.top) - obj.height + obj.top - obj.top - obj.cornerSize / 2);
obj.left = Math.min(obj.left, (map.width + map.left) - obj.width + obj.left - obj.left - obj.cornerSize / 2);
}
});
//zoom
let clicks = 0;
floorPlan.on('mouse:down', function(event) {
clicks += 1;
setTimeout(function() {
if (clicks > 1) {
console.log(event.e.clientX);
floorPlan.zoomToPoint(new fabric.Point(event.e.clientX, event.e.clientY), floorPlan.getZoom() + 1);
}
clicks = 0;
}, 300);
});
function zoomIn() {
floorPlan.setZoom(floorPlan.getZoom() + 0.5);
console.log(floorPlan.getZoom(), defaultScale * floorPlan.getZoom());
setBackground(defaultScale / floorPlan.getZoom());
floorPlan.renderAll();
}
function zoomOut() {
floorPlan.setZoom(floorPlan.getZoom() - 0.5); ;
console.log(floorPlan.getZoom(), defaultScale * floorPlan.getZoom());
setBackground(defaultScale / floorPlan.getZoom());
floorPlan.renderAll();
}
function renderAll() {
floorPlan.renderAll();
}
//pan
function pan(event) {
event.e.preventDefault();
let panAmount = {
x: event.e.deltaX,
y: event.e.deltaY
};
//logic for binding the map to the screen during pan
/*
* We need to use the following points and check for negative or overflow values
* canvas.oCoords.tl - top left
* canvas.oCoords.bl - bottom left
* canvas.oCoords.tr - top right
* canvas.oCoords.br - bottom right
*/
//pan left
if (panAmount.x > 0) {
if (map.oCoords.tr.x < floorPlan.width - 40) {
panAmount.x = 0;
}
}
//pan right
if (panAmount.x < 0) {
if (map.oCoords.tl.x > 40) {
panAmount.x = 0;
}
}
//pan up
if (panAmount.y > 0) {
if (map.oCoords.bl.y < floorPlan.height - 40) {
panAmount.y = 0;
}
}
//pan down
if (panAmount.y < 0) {
if (map.oCoords.tl.y > 40) {
panAmount.y = 0;
}
}
// assuming the logic above allows the scroll, get the relative scroll values and create a new fabric point and then scroll to it
const delta = new fabric.Point(-panAmount.x, -panAmount.y);
floorPlan.relativePan(delta);
}
floorPlan.on('mouse:wheel', pan);
Related
I am a noob in javascript so anyone please help me, When I zoomed in on the image and if the mouse out from that image it will return its original size how we did that through resize method that given in "imageviewer2.js".
The image is zoomed in million times but when the mouse cursor is out from the image div it will not return its original size how we can do it by resize method.
I did that mouseout method but that is a patchwork so I want with the resize method but I can't understand how I can do that from resize method.
/*
* imgViewer2
*
*
* Copyright (c) 2013 Wayne Mogg
* Licensed under the MIT license.
*/
var waitForFinalEvent = (function () {
var timers = {};
return function (callback, ms, uniqueId) {
if (!uniqueId) {
uniqueId = "Don't call this twice without a uniqueId";
}
if (timers[uniqueId]) {
clearTimeout (timers[uniqueId]);
}
timers[uniqueId] = setTimeout(callback, ms);
};
})();
/*
* imgViewer2 plugin starts here
*/
;(function($) {
$.widget("wgm.imgViewer2", {
options: {
zoomStep: 0.5,
zoomMax: undefined,
zoomable: true,
dragable: true,
onClick: $.noop,
onReady: $.noop
},
_create: function() {
var self = this;
if (!$(this.element).is("img")) {
var elem = this.element.children()[0];
if (!$(elem).is("img")) {
$.error('imgviewer plugin can only be applied to img elements');
} else {
self.img = self.element.children()[0];
}
} else {
self.img = self.element[0];
}
// the original img element
var $img = $(self.img);
/*
* a copy of the original image to be positioned over it and manipulated to
* provide zoom and pan
*/
self.view = $("<div class='viewport' />").uniqueId().appendTo($img.parent());
var $view = $(self.view);
self.map = {};
self.bounds = {};
// a flag used to check the target image has loaded
self.ready = false;
self.resize = false;
$img.one("load",function() {
// get and some geometry information about the image
self.ready = true;
var width = $img.width(),
height = $img.height(),
offset = $img.position();
// cache the image padding information
self.offsetPadding = {
top: parseInt($img.css('padding-top'),10),
left: parseInt($img.css('padding-left'),10),
right: parseInt($img.css('padding-right'),10),
bottom: parseInt($img.css('padding-bottom'),10)
};
/*
* cache the image margin/border size information
* because of IE8 limitations left and right borders are assumed to be the same width
* and likewise top and bottom borders
*/
self.offsetBorder = {
x: Math.round(($img.outerWidth()-$img.innerWidth())/2),
y: Math.round(($img.outerHeight()-$img.innerHeight())/2)
};
/*
* define the css style for the view container using absolute positioning to
* put it directly over the original image
*/
var vTop = offset.top + self.offsetBorder.y + self.offsetPadding.top,
vLeft = offset.left + self.offsetBorder.x + self.offsetPadding.left;
$view.css({
position: "absolute",
overflow: "hidden",
top: vTop+"px",
left: vLeft+"px",
width: width+"px",
height: height+"px"
});
// add the leaflet map
self.bounds = L.latLngBounds(L.latLng(0,0), L.latLng(self.img.naturalHeight,self.img.naturalWidth));
self.map = L.map($view.attr('id'), {crs:L.CRS.Simple,
minZoom: -10,
trackresize: false,
maxBoundsViscosity: 1.0,
attributionControl: false,
inertia: false,
zoomSnap: 0,
wheelPxPerZoomLevel: Math.round(36/self.options.zoomStep),
zoomDelta: self.options.zoomStep
});
self.zimg = L.imageOverlay(self.img.src, self.bounds).addTo(self.map);
self.map.options.minZoom = self.map.getBoundsZoom(self.bounds,false);
self.map.fitBounds(self.bounds);
self.bounds = self.map.getBounds();
self.map.setMaxBounds(self.bounds);
if (self.options.zoomMax !== null) {
var lzoom = self.leafletZoom(self.options.zoomMax);
if (lzoom < self.map.getZoom()) {
self.map.setZoom(lzoom);
}
self.map.options.maxZoom = lzoom;
}
if (!self.options.dragable) {
self.map.dragging.disable();
}
if (!self.options.zoomable) {
self.map.zoomControl.disable();
self.map.boxZoom.disable();
self.map.touchZoom.disable();
self.map.doubleClickZoom.disable();
self.map.scrollWheelZoom.disable();
}
self.map.on('click', function(ev) {
if (self.options.onClick !== null) {
self.options.onClick.call(self, ev.originalEvent, self.eventToImg(ev));
}
});
self.map.on('zoomend', function() {
if (self.options.zoomMax >= 1 && this.getZoom() > this.options.zoomMax) {
this.setZoom(this.options.zoomMax);
}
if (!self.resize) {
self.bounds = self.map.getBounds();
}
});
self.map.on('moveend', function() {
if (!self.resize) {
self.bounds = self.map.getBounds();
}
});
self.map.on('resize', function() {
self.map.options.minZoom = -10;
self.map.fitBounds(self.bounds,{animate:false});
self.map.options.minZoom = self.map.getBoundsZoom(L.latLngBounds(L.latLng(0,0), L.latLng(self.img.naturalHeight,self.img.naturalWidth)),true);
self.map.options.maxZoom = self.leafletZoom(self.options.zoomMax);
waitForFinalEvent(function(){
self.resize = false;
self._view_resize();
self.map.options.minZoom = -10;
self.map.fitBounds(self.bounds,{animate:false});
self.map.options.minZoom = self.map.getBoundsZoom(L.latLngBounds(L.latLng(0,0), L.latLng(self.img.naturalHeight,self.img.naturalWidth)),true);
self.map.options.maxZoom = self.leafletZoom(self.options.zoomMax);
}, 300, $img[0].id);
});
self.options.onReady.call(self);
}).each(function() {
if (this.complete) { $(this).trigger("load"); }
});
/*
/*
* Window resize handler
*/
$(window).resize(function() {
if (self.ready) {
self.resize = true;
self._view_resize();
self.map.invalidateSize({animate: false});
}
});
},
/*
* View resize - the aim is to keep the view centered on the same location in the original image
*/
_view_resize: function() {
if (this.ready) {
var $view = $(this.view),
$img = $(this.img),
width = $img.width(),
height = $img.height(),
offset = $img.position(),
vTop = Math.round(offset.top + this.offsetBorder.y + this.offsetPadding.top),
vLeft = Math.round(offset.left + this.offsetBorder.x + this.offsetPadding.left);
$view.css({
top: vTop+"px",
left: vLeft+"px",
width: width+"px",
height: height+"px"
});
}
},
/*
* Remove the plugin
*/
destroy: function() {
$(window).unbind("resize");
this.map.remove();
$(this.view).remove();
$.Widget.prototype.destroy.call(this);
},
_setOption: function(key, value) {
switch(key) {
case 'zoomStep':
if (parseFloat(value) <= 0 || isNaN(parseFloat(value))) {
return;
}
break;
case 'zoomMax':
if (parseFloat(value) < 1 || isNaN(parseFloat(value))) {
return;
}
break;
}
var version = $.ui.version.split('.');
if (version[0] > 1 || version[1] > 8) {
this._super(key, value);
} else {
$.Widget.prototype._setOption.apply(this, arguments);
}
switch(key) {
case 'zoomStep':
if (this.ready) {
this.map.options.zoomDelta = this.options.zoomStep;
this.map.options.wheelPxPerZoomLevel = Math.round(60/this.options.zoomStep);
}
break;
case 'zoomMax':
if (this.ready) {
lzoom = this.leafletZoom(this.options.zoomMax);
if (lzoom < this.map.getZoom()) {
this.map.setZoom(lzoom);
}
this.map.options.maxZoom = lzoom;
this.map.fire('zoomend');
}
break;
case 'zoomable':
if (this.options.zoomable) {
this.map.zoomControl.enable();
this.map.boxZoom.enable();
this.map.touchZoom.enable();
this.map.doubleClickZoom.enable();
this.map.scrollWheelZoom.enable();
} else {
this.map.zoomControl.disable();
this.map.boxZoom.disable();
this.map.touchZoom.disable();
this.map.doubleClickZoom.disable();
this.map.scrollWheelZoom.disable();
}
break;
case 'dragable':
if (this.options.dragable) {
this.map.dragging.enable();
} else {
this.map.dragging.disable();
}
break;
}
},
/*
* Test if a relative image coordinate is visible in the current view
*/
isVisible: function(relx, rely) {
var view = this.getView();
if (view) {
return (relx >= view.left && relx <= view.right && rely >= view.top && rely <= view.bottom);
} else {
return false;
}
},
/*
* Convert a user supplied zoom to a Leaflet zoom
*/
leafletZoom: function(zoom) {
if (this.ready && zoom !== undefined) {
var img = this.img,
map = this.map,
lzoom = map.getZoom() || 0,
size = map.getSize(),
width = img.naturalWidth,
height = img.naturalHeight,
nw = L.latLng(height/zoom,width/zoom),
se = L.latLng(0,0),
boundsSize = map.project(nw, lzoom).subtract(map.project(se, lzoom));
var scale = Math.min(size.x / boundsSize.x, -size.y / boundsSize.y);
return map.getScaleZoom(scale, lzoom);
} else {
return undefined;
}
},
/*
* Get the Leaflet map object
*/
getMap: function() {
if (this.ready) {
return this.map;
}
else {
return null;
}
},
/*
* Get current zoom level
* Returned zoom will always be >=1
* a zoom of 1 means the entire image is just visible within the viewport
* a zoom of 2 means half the image is visible in the viewport etc
*/
getZoom: function() {
if (this.ready) {
var img = this.img,
map = this.map,
width = img.naturalWidth,
height = img.naturalHeight,
constraint = this.options.constraint,
bounds = map.getBounds();
if (constraint == 'width' ) {
return Math.max(1, width/(bounds.getEast()-bounds.getWest()));
} else if (constraint == 'height') {
return Math.max(1,height/(bounds.getNorth()-bounds.getSouth()));
} else {
return Math.max(1, (width/(bounds.getEast()-bounds.getWest()) + height/(bounds.getNorth()-bounds.getSouth()))/2);
}
} else {
return null;
}
},
/*
* Set the zoom level
* Zoom must be >=1
* a zoom of 1 means the entire image is just visible within the viewport
* a zoom of 2 means half the image is visible in the viewport etc
*/
setZoom: function( zoom ) {
if (this.ready) {
zoom = Math.max(1, zoom);
if (this.options.zoomMax === undefined) {
} else {
zoom = Math.min(zoom, this.options.zoomMax);
}
var img = this.img,
map = this.map,
width = img.naturalWidth,
height = img.naturalHeight,
constraint = this.options.constraint,
center = map.getCenter(),
bounds = map.getBounds();
var hvw, hvh;
if (constraint == 'width') {
hvw = width/zoom/2;
hvh = hvw * (bounds.getNorth()-bounds.getSouth())/(bounds.getEast()-bounds.getWest());
} else if (constraint == 'height') {
hvh = height/zoom/2;
hvw = hvh * (bounds.getEast()-bounds.getWest())/(bounds.getNorth()-bounds.getSouth());
} else {
hvw = width/zoom/2;
hvh = height/zoom/2;
}
var east = center.lng + hvw,
west = center.lng - hvw,
north = center.lat + hvh,
south = center.lat - hvh;
if (west<0) {
east += west;
west = 0;
} else if (east > width) {
west -= east-width;
east = width;
}
if (south<0) {
north += south;
south = 0;
} else if (north > height) {
south -= north-height;
north = height;
}
map.fitBounds(L.latLngBounds(L.latLng(south,west), L.latLng(north,east)),{animate:false});
}
return this;
},
/*
* Get relative image coordinates of current view
*/
getView: function() {
if (this.ready) {
var img = this.img,
width = img.naturalWidth,
height = img.naturalHeight,
bnds = this.map.getBounds();
return {
top: 1 - bnds.getNorth()/height,
left: bnds.getWest()/width,
bottom: 1 - bnds.getSouth()/height,
right: bnds.getEast()/width
};
} else {
return null;
}
},
/*
* Pan the view to be centred at the given relative image location
*/
panTo: function(relx, rely) {
if ( this.ready && relx >= 0 && relx <= 1 && rely >= 0 && rely <=1 ) {
var img = this.img,
map = this.map,
bounds = this.bounds,
// bounds = map.getBounds(),
east = bounds.getEast(),
west = bounds.getWest(),
north = bounds.getNorth(),
south = bounds.getSouth(),
centerX = (east+west)/2,
centerY = (north+south)/2,
width = img.naturalWidth,
height = img.naturalHeight,
newY = (1-rely)*height,
newX = relx*width;
east += newX - centerX;
west += newX - centerX;
north += newY - centerY;
south += newY - centerY;
if (west<0) {
east -= west;
west = 0;
}
if (east > width) {
west -= east-width;
east = width;
}
if (south<0) {
north -= south;
south = 0;
}
if (north > height) {
south -= north-height;
north = height;
}
map.fitBounds(L.latLngBounds(L.latLng(south,west), L.latLng(north,east)),{animate:false});
}
return this;
},
/*
* Return the relative image coordinate for a Leaflet event
*/
eventToImg: function(ev) {
if (this.ready) {
var img = this.img,
width = img.naturalWidth,
height = img.naturalHeight;
relx = ev.latlng.lng/width;
rely = 1 - ev.latlng.lat/height;
if (relx>=0 && relx<=1 && rely>=0 && rely<=1) {
return {x: relx, y: rely};
} else {
return null;
}
} else {
return null;
}
},
/*
* Convert relative image coordinate to Leaflet LatLng point
*/
relposToLatLng: function(x,y) {
if (this.ready) {
var img = this.img,
width = img.naturalWidth,
height = img.naturalHeight;
return L.latLng((1-y)*height, x*width);
} else {
return null;
}
},
/*
* Convert relative image coordinate to Image pixel
*/
relposToImage: function(pos) {
if (this.ready) {
var img = this.img,
width = img.naturalWidth,
height = img.naturalHeight;
return {x: Math.round(pos.x*width), y: Math.round(pos.y*height)};
} else {
return null;
}
}
});
})(jQuery);
<!DOCTYPE html>
<html>
<head>
<title>imgViewer2 Plugin - custom onClick callback example</title>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" media="screen">
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.0.3/dist/leaflet.css" />
<script type="text/javascript" src="https://unpkg.com/leaflet#1.0.3/dist/leaflet.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript" src="lib/imgViewer2.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes" />
</head>
<body>
<table cellspacing="0" cellpadding="0" border="0" style="width: 100%; min-width: 320px;">
<tr>
<td style="padding: 10px">
<h1 align="center">imgViewer2 Plugin - custom onClick callback example</h1>
<div align="center">
<img id="image1" src="https://media.gettyimages.com/photos/laughing-black-man-wearing-backpack-near-purple-door-picture-id573064079" style="border: 30px solid #555; padding:20px;" width="80%" />
</div>
<p style="margin:10px 5% 10px 5%;text-align: justify;">
This example demonstrates the widget with a custom "onClick" callback. In this case clicking on the image will show the page pixel coordinates, image pixel coordinates and relative image coordinates (relative image coordinates range from 0,0 to 1,1 where 0,0 is the top-left corner and 1,1 is the bottom-right corner of the image) in a modal dialog.
</p>
<p style="margin:10px 5% 10px 5%;text-align: justify;">
Zoom in and out using the mousewheel, +/- controls, double click/tap or hold the shift key and drag out a rectangle. Left mouseclick and drag to pan. On touch enable devices pinch gestures can be used to zoom in and out and tap and drag to pan around.
</p>
<p style="margin:10px 5% 10px 5%;">Resize the window and try again</p>
</td>
</tr>
</table>
<script type="text/javascript">
;(function($) {
$(document).ready(function(){
var $img = $("#image1").imgViewer2({
onReady: function() {
$('.leaflet-grab').css('cursor','crosshair');
},
onClick: function( e, pos ) {
var $message = $("<div id='dialog-modal'></div>").dialog({
modal: true,
title: "You clicked at:",
open: function(ev, ui) {
$('.ui-dialog').css('z-index',2001);
$('.ui-widget-overlay').css('z-index',2000);
}
});
var imgpos = this.relposToImage(pos);
$message.html("Page X: " + e.pageX + "<br/>Page Y: " + e.pageY + "<br/>Image Pixel X: " + imgpos.x + "<br/>Image Pixel Y: " + imgpos.y + "<br/>Image Rel X: " + pos.x.toFixed(3) + "<br/>Image Rel Y: " + pos.y.toFixed(3));
}
});
});
})(jQuery);
</script>
</body>
</html>
I'm using Fabric.js to create a group of two rectangles that are lying next to each other. I want the left one to change it's color when I'm moving my mouse over it. Therefore I check if the position of the mouse-cursor is within the the boundary of the rectangle or not.
This works fine until I scale the group...
I made a few tests and found out that the properties of the group members don't change. So while the rectangles become larger they still show their old sizes.
This is my code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
//Show the sizes
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth());
console.log("rect2 width: " + e.target.item(1).getWidth());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
//Check if cursor is within the boundary of rect2
if(point.posX >= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2
&& point.posX <= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2 + e.target.item(1).getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.item(1).getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also made a Fiddle: https://jsfiddle.net/werschon/0uduqfpe/3/
If I make the group larger, my 'mouse-detection' doesn't work anymore, because the properties like left, top or width are not updating.
What do I need to do, to update the properties? Or is there another way of detecting if the mouse-cursor is above the rectangle?
Thanks.
I found a solution (Thanks to John Morgan): I have to calculate the properties of the group members by myself. With getScaleX() and getScaleY() I can get the scale-factors of the group. Then I need to multiply the properties of the group members with the appropriate scale-factor. As result I get the new values of the group member. In my example I only needed getScaleX() to calculate the width of rect2 so it looks like this: e.target.item(1).getWidth() * e.target.getScaleX().
This is the new code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth() * e.target.getScaleX());
console.log("rect2 width: " + e.target.item(1).getWidth() * e.target.getScaleX());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
if(point.posX >= e.target.getLeft() + e.target.getWidth() - e.target.item(1).getWidth() * e.target.getScaleX()
&& point.posX <= e.target.getLeft() + e.target.getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
e.target.item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
farbicCanvas.on('mouse:out', function(e)
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also created a new Fiddle. So now my mouse-detection works even after scaling
I want to make and dynamic crop area and found this snippet. It works perfect in normal usage, but when you scaled the original object before making the crop area, the crop zone seems not in the right position. Can you look into this pen for some help ?
var canvas = new fabric.CanvasEx('canvas');
var el;
var object, lastActive, object1, object2;
var cntObj = 0;
var selection_object_left = 0;
var selection_object_top = 0;
var src = "http://fabricjs.com/lib/pug.jpg";
fabric.Image.fromURL('https://omicron.aeon.co/images/08e7f2bb-f2ce-4058-a955-1c8d594468a2/card_SIZED-Aleksandr-Zykov-4975950437_b84f9f9ef8_o.jpg', function (oImg) {
oImg.top = canvas.getHeight()/2 - oImg.getHeight()/2;
oImg.left = canvas.getWidth()/2 - oImg.getWidth()/2;
canvas.add(oImg);
bindCropEvent(oImg);
});
canvas.renderAll();
function bindCropEvent(obj){
obj.on('object:dblclick', function(){
CropMode();
});
};
function CropMode() {
canvas.remove(el);
if (canvas.getActiveObject()) {
object = canvas.getActiveObject();
if (lastActive !== object) {
console.log('different object');
} else {
console.log('same object');
}
if (lastActive && lastActive !== object) {
//lastActive.clipTo = null; results in clip loss
}
el = new fabric.Rect({
fill: 'rgba(0,0,0,0.6)',
originX: 'left',
originY: 'top',
stroke: '#ccc',
strokeDashArray: [2, 2],
opacity: 1,
width: 1,
height: 1,
borderColor: 'red',
cornerColor: 'red',
hasRotatingPoint: false
});
el.left = canvas.getActiveObject().left;
selection_object_left = canvas.getActiveObject().left;
selection_object_top = canvas.getActiveObject().top;
el.top = canvas.getActiveObject().top;
el.width = canvas.getActiveObject().width * canvas.getActiveObject().scaleX;
el.height = canvas.getActiveObject().height * canvas.getActiveObject().scaleY;
//插入
canvas.add(el);
canvas.setActiveObject(el);
el.on('deselected', function(){
console.log('des');
doCrop();
});
} else {
alert("Please select an object or layer");
}
}
function doCrop() {
var eLeft = el.get('left');
var eTop = el.get('top');
var left = eLeft - object.left;
var top = eTop - object.top;
console.log(left, top);
left *= 1;
top *= 1;
console.log(left, top);
var eWidth = el.get('width');
var eHeight = el.get('height');
var eScaleX = el.get('scaleX');
var eScaleY = el.get('scaleY');
var width = eWidth * 1;
var height = eHeight * 1;
object.clipTo = function (ctx) {
ctx.rect(-(eWidth / 2) + left, -(eHeight / 2) + top, parseInt(width * eScaleX), parseInt( height * eScaleY));
}
canvas.remove(el);
lastActive = object;
canvas.renderAll();
}
Thanks !
when you create a rect, you can create new image with toDataURL(). What will be cropped image.
cropOptions = {
left: Math.floor(rect.left),
top: Math.floor(rect.top),
width: Math.floor(rect.width),
height: Math.floor(rect.height)
},
cropDataUrl ;
cropDataUrl = image.toDataURL(cropOptions);
new fabric.Image.fromURL(cropDataUrl, function(img) {
canvas.remove(image,rect).add(img); //this is your cropped image
})
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).
I have created a random fishes animation but at some point (about 10sec) from the beginning, my fishes Cling to the right, i want them to keep moving in random all over the area, any idea?
here is the fiddle link: http://jsfiddle.net/832Fx/1/
jquery code:
$.fn.rotate = function (degrees) {
var self = $(this);
self.transition({
rotateY: degrees + 'deg'
}, 5000);
};
var animeVars = {
maxPixelsPerSecond: 50,
minPixelsPerSecond: 10,
topMargin: 0,
bottomMargin: 400,
leftMargin: 0,
rightMargin: 400
};
function topFlip(obj) {
var speed = $(obj).data('speed') ? (1 / parseFloat($(obj).data('speed'))) * 1000000 : 300;
$(obj).find('.top_fin, .top_fins').transition({
rotate: '+=25deg',
x: '+=10'
}, speed, function () {
$(obj).find('.top_fin, .top_fins').transition({
rotate: '-=25deg',
x: '-=10'
}, speed, function () {
topFlip(obj);
});
});
}
function tailFlip(obj) {
var speed = $(obj).data('speed') ? (1 / parseFloat($(obj).data('speed'))) * 1000000 : 300;
$(obj).find('.tail_fin, .tail_fins').transition({
rotateX: '-=25deg'
}, speed, function () {
$(obj).find('.tail_fin, .tail_fins').transition({
rotateX: '+=25deg'
}, speed, function () {
tailFlip(obj);
});
});
}
function animateFish(obj) {
var heading = $(obj).data('heading');
if (!heading) heading = 'left';
var rotation = 0;
var currentCoords = {
top: parseInt($(obj).css('top').replace(/[^-\d\.]/g, ''), 10),
left: parseInt($(obj).css('left').replace(/[^-\d\.]/g, ''), 10)
};
var newCoords = {
top: Math.random() * (animeVars.topMargin - animeVars.bottomMargin + 1) + animeVars.bottomMargin,
left: Math.random() * (animeVars.leftMargin - animeVars.rightMargin + 1) + animeVars.rightMargin
};
if (currentCoords.left < newCoords.left && heading != 'right') {
$(obj).rotate(180);
$(obj).data('heading', 'right');
console.log('swimming right');
} else if (currentCoords.left > newCoords.left && heading != 'left') {
$(obj).rotate(0);
$(obj).data('heading', 'left');
console.log('swimming left');
}
var totalMovement = Math.sqrt(Math.pow(currentCoords.left - newCoords.left, 2) + Math.pow(currentCoords.top - newCoords.top, 2));
console.log('Total pixels to move: ' + totalMovement);
var pps = Math.floor(Math.random() * (animeVars.maxPixelsPerSecond - animeVars.maxPixelsPerSecond)) + animeVars.maxPixelsPerSecond;
var speed = totalMovement / pps * 1000;
$(obj).data('speed', speed);
$(obj).animate({
top: newCoords.top,
left: newCoords.left
}, speed, function () {
animateFish(obj);
});
}
$(document).ready(function () {
$('.loop ').each(function () {
animateFish(this);
topFlip(this);
tailFlip(this);
});
});
Here is how I would fix this: http://jsfiddle.net/BaliBalo/832Fx/2/
I just added
var $wnd = $(window);
$wnd.resize(function() {
animeVars.rightMargin = $wnd.width();
animeVars.bottomMargin = $wnd.height();
}).resize();
at the end of your code, and changed the algorithm a bit to take in account fishes size:
var w = $(obj).width();
var h = $(obj).height();
var newCoords = {
top: Math.random() * (animeVars.topMargin - animeVars.bottomMargin + h + 1) + animeVars.bottomMargin - h,
left: Math.random() * (animeVars.leftMargin - animeVars.rightMargin + w + 1) + animeVars.rightMargin - w
};
However, please note that many optimizations could be done, like caching jQuery objects in variables rather than calling the $ function every time.