fabricjs mouse events fail after setSrc() - image

When changing a selected canvas image to a larger image using setSrc(), mouse events are not recognized on areas of the new image that lie outside of the xy boundaries of the original smaller image.
Conversely, when changing a selected canvas image to a smaller image, mouse events are recognized on areas outside of the new image up to the xy boundaries of the original larger image.
In either case, once the new image does receive a mouse click, things return to normal where the entire image receives mouse events, no more, no less. Also, the controls appear in the correct locations but are not clickable as stated above.
Is there a way to correct this behavior so only the complete visible image can receive mouse events?
http://jsfiddle.net/kamakalama/0ng18cas/10/
HTML
<body>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<button id="butt">Change Image</button>
<canvas style="border:1px solid silver;" width=500 height=400 id="c"></canvas>
</body>
JavaScript
$(document).ready(function() {
var canvas = new fabric.Canvas('c',{backgroundColor:"#ffffff"});
var bigImg="http://nonsuch30.com/yachtcards/images/cardbox-closed.jpg"
var smallImg="http://nonsuch30.com/yachtcards/images/woodcrafter-thumb.jpg"
fabric.Image.fromURL(smallImg, function (img) {
canvas.add(img);
canvas.renderAll();
img.setTransformMatrix([ 1, 0, 0, 1, 0, 0])
canvas.setActiveObject(img)
})
$("#butt").click(function(){
img = canvas.getActiveObject()
if(img.getSrc()==smallImg){
img.setSrc(bigImg)
}else{
img.setSrc(smallImg)
}
setTimeout(function(){
canvas.renderAll()
canvas.setActiveObject(img)
}, 2000);
});
});

You're correct in using setCoords(), but put that and canvas.renderAll() in the setSrc() callback so you can remove the setTimeout() function call:
if (img.getSrc() == smallImg) {
img.setSrc(bigImg, function() {
canvas.renderAll();
img.setCoords();
});
} else {
img.setSrc(smallImg, function() {
canvas.renderAll();
img.setCoords();
});
}

Calling setCoords() on the new image corrects the problem.

Related

Export Canvas with Raster Image to png with Paper.js

I am new to Paper.js, I am creating a simple drawing tool using paper.js and download the canvas with the background image. I'm trying filesaver.js and blob however, I'm stuck downloading the canvas with a raster image as background.
Any help or suggestions would be highly appreciated.
I tried:
'''
HTML:
//button to download
<button href="#" class="btn btn-primary" id="save-canvas"
onclick="exportPNG()">Save</button>
//canvas
<canvas class="canvas" resize id="canvas"></canvas>
//image
<img src="image/image.jpg" id="image">
JS:
function exportPNG() {
var canvas = document.getElementById('canvas');
canvas.toBlob(function (blob) {
saveAs(blob, 'paper.png');
});
}
// Setup Paper
paper.setup(document.querySelector('canvas'));
//Image Layer
var imageLayer = new Layer({insert: false});
var image = new Raster('image');
image.position = view.center;
project.layers.push(imageLayer);
imageLayer.addChild(image);
imageLayer.activate();
'''
If I understand your request, you can do this quite simply.
There are some gotchas though like the fact that you need to wait for the image to be loaded and for the scene to be drawn before exporting the canvas content as image...
Here is a sketch demonstrating one way of doing it.
// Create a raster that will be used as background.
const raster = new Raster({
crossOrigin: 'anonymous',
source: 'http://assets.paperjs.org/images/marilyn.jpg',
position: view.center
});
// Wait for the raster to be loaded...
raster.onLoad = () => {
// ...and center it.
raster.position = view.center;
// Draw a circle over it.
new Path.Circle({
center: view.center,
radius: 50,
fillColor: 'blue'
});
// Make sure that the scene is drawn on the canvas.
view.update();
// Create a blob from the canvas...
view.element.toBlob((blob) => {
// ...and get it as a URL.
const url = URL.createObjectURL(blob);
// Open it in a new tab.
window.open(url, '_blank');
});
};

Skrollr: Smooth scrolling

I would like to achieve a smooth scrolling when turning a mouse wheel. Currently, when I do one turn, the scrollbar kinda jumps and the animation is jumpy.
Example of this behaviour:
http://areaaperta.com/nicescroll/
Can this scrolling be achieved using skrollr only? If so, how?
I tried following code
var s = skrollr.init({
render: function(data) {
//Debugging - Log the current scroll position.
console.log(data.curTop);
},
smoothScrolling: true,
smoothScrollingDuration: 500,
easing: {
WTF: Math.random,
inverted: function(p) {
return 1-p;
}
}
});
but it doesn't make a big difference. The animation is little bit smoother (i.e. background slides for a while and then stops), but the scrolling itself is still jumpy.
I would prefer to solve this with skrollr only as I think it is prepared for it instead of adding another plugin.
This is a quote from Petr Tichy (ihatetomatoes.net):
For smooth animations, animate cheap properties.
The best result you'll get, when you keep animating only the cheap CSS
properties.
transform: scale(1.2)
transform: translateX(100px)
transform: rotate(90deg)
opacity: 0.5
These 4 properties will let you change the size, position, rotation
and opacity of your elements.
Combination of these CSS properties will enable you to create pretty
much most of you ideas and will get you the best results.
If you come across lagging and choppy scrolling animations, give the
animated element transform: translateZ(0).
This will promote the element into composite layers and will get rid
of the lag.
Try to include this script
jQuery(function () {
var $window = jQuery(window);
var scrollTime = 0.5;
var scrollDistance = 150;
$window.on("mousewheel DOMMouseScroll", function (event) {
event.preventDefault();
var delta = event.originalEvent.wheelDelta / 120 || -event.originalEvent.detail / 3;
var scrollTop = $window.scrollTop();
var finalScroll = scrollTop - parseInt(delta * scrollDistance);
TweenMax.to($window, scrollTime, {
scrollTo: {
y: finalScroll,
autoKill: true
},
ease: Power1.easeOut,
overwrite: 5
});
});
});
I had this problem also (With Chrome on Mac)
I solved by this plug-in :
https://github.com/simov/simplr-smoothscroll
<!-- After jQuery -->
<script src="jquery.mousewheel.min.js"></script>
<script src="jquery.simplr.smoothscroll.min.js"></script>
<script type="text/javascript">$.srSmoothscroll();</script>

KineticJS - How can we move an image behind a mask without moving mask

Is it possible to move an image behind a mask without moving the mask itself ? I am looking for an operation which allow to move an image behind a mask and it should be accurate and smooth.
The best answer I am looking is to mask an Kinetic.Image object. Kinetic.Image is draggable and need to worry about it's movement. Please let me know if it's really possible to mask Kinetic.Image Object ?
A Demo: http://jsfiddle.net/m1erickson/u28MS/
Use a Kinetic.Shape to get access to the canvas context and then create a clipping region.
Create a new Kinetic.Shape
Define your non-rectangular path in the shape
Call clip() to restrict drawing to that path.
Draw the image into the clipping region.
Give the image x & y properties so that the image can be draw
Here's what that looks like in code:
// create a Kinetic.Shape which gives you access
// to a context to draw on
clippingShape = new Kinetic.Shape({
sceneFunc: function(context) {
// define your path here
// context.beginPath(); ...
// make your path a clipping region
context.clip();
// draw the image inside the clipping region
// img.x & img.y are offsets which can be used
// to "drag" the image around the clipping region
context.drawImage(img,img.x,img.y);
// KineticJS specific context method
context.fillStrokeShape(this);
},
stroke: 'black',
strokeWidth: 4,
listening:false
});
Listen for mouse events on the stage to cause the image to reposition when drawn in the Shape.
In mousedown: Save the mouse position and set a flag indicating the drag has begun.
In mousemove: Calc how much the mouse has moved and offset the image's x/y by that distance.
In mouseup: clear the dragging flag since the drag is over.
The mouse event handlers look like this:
var isdown=false;
stage.getContent().onmousedown=function(e){
var pos=stage.getPointerPosition();
img.lastX=parseInt(pos.x);
img.lastY=parseInt(pos.y);
isdown=true;
};
stage.getContent().onmouseup=function(e){
isdown=false;
};
stage.getContent().onmousemove=function(e){
if(!isdown){return;}
var pos=stage.getPointerPosition();
var mouseX=parseInt(pos.x);
var mouseY=parseInt(pos.y);
var dx=mouseX-img.lastX;
var dy=mouseY-img.lastY;
img.lastX=mouseX;
img.lastY=mouseY;
img.x+=dx;
img.y+=dy;
layer.draw();
};
[ Previous version of answer -- replaced with new answer above after questioners commments ]
This kind of clipping is traditionally done with a foreground image that contains a transparent "viewport" which lets the user see a portion of the background image beneath.
A Demo: http://jsfiddle.net/m1erickson/2f9yu/
Create a draggable background image on a bottom layer:
// create a background layer
var bottomLayer=new Kinetic.Layer();
stage.add(bottomLayer);
// put a draggable image on the background layer
var city=new Kinetic.Image({ image:bk,x:0,y:0,draggable:true,width:700,height:440, });
bottomLayer.add(city);
bottomLayer.draw();
Create a non-draggable foreground image on a top layer.
The top image has a transparent "viewport".
Important: the top layer does not listen for events, so dragging moves the bottom image, not the top image.
// create a top layer that does not respond to mouse events
// any mouse events will filter down to the background image
// this enables the background to be dragged even while behind the top image
var topLayer=new Kinetic.Layer({listening:false,});
stage.add(topLayer);
// create a top image with transparent pixels
// used as a viewport to see a portion of the bottom image
var mirror=new Kinetic.Image({ image:viewport,x:0,y:0 });
topLayer.add(mirror);
topLayer.draw();
Example code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:350px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
var bottomLayer=new Kinetic.Layer();
stage.add(bottomLayer);
var topLayer=new Kinetic.Layer({listening:false,});
stage.add(topLayer);
var loadedCount=0;
//
var bk=new Image();
bk.onload=start;
bk.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/desert1.jpg";
//
var viewport=new Image();
viewport.onload=start;
viewport.src="https://dl.dropboxusercontent.com/u/139992952/multple/car4.png";
function start(){
if(++loadedCount<2){return;}
var city=new Kinetic.Image({ image:bk,x:0,y:0,draggable:true,width:700,height:440, });
bottomLayer.add(city);
bottomLayer.draw();
var mirror=new Kinetic.Image({ image:viewport,x:0,y:0 });
topLayer.add(mirror);
topLayer.draw();
}
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag to move the background image in the mirror</h4>
<div id="container"></div>
</body>
</html>

Draw a rectangle with fill color and individual borders

How can I draw a canvas rectangle with fill color AND 4 different border colors on each side?
You can add a new method to canvas.context that draws your multi-colored rectangle.
You define a new method on the canvas.context through its prototype:
CanvasRenderingContext2D.prototype.myNewMethod = function(){ ... };
Inside the new method, you can use any context drawing commands to draw your desired shape.
Note that inside myNewMethod, “this” refers to the canvas.context, so you draw like this:
this.lineTo(x,y) // not context.lineTo
Your fancy rectangle is a fairly straightforward drawing, except for mitered side strokes.
Each side stroke is drawn as a filled trapezoid:
function trapezoid(context,color,x1,y1,x2,y2,x3,y3,x4,y4){
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.lineTo(x3,y3);
context.lineTo(x4,y4);
context.closePath();
context.fillStyle=color;
context.fill();
}
Your fancy new rectangle method (rainbowRect) is called just like context.fillRect.
context.rainbowRect(100,50,100,50,"gold","red","blue","green","purple");
Here is a complete example:
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// Add a rainbowRect function to the context prototype
// This method is used alone like context.fillRect
// This method is not used within a context.beginPath
// NOTE: this addition must always be run before it is used in code
CanvasRenderingContext2D.prototype.rainbowRect = function (x,y,w,h,fillColor,tColor,rColor,bColor,lColor){
// use existing fillStyle if fillStyle is not supplied
fillColor=fillColor||this.fillStyle;
// use existing strokeStyle if any strokeStyle is not supplied
var ss=this.strokeStyle;
tColor=tColor||ss;
rColor=rColor||ss;
bColor=bColor||ss;
lColor=lColor||ss;
// context will be modified, so save it
this.save();
// miter the lines
this.lineJoin="miter";
// helper function: draws one side's trapezoidal "stroke"
function trapezoid(context,color,x1,y1,x2,y2,x3,y3,x4,y4){
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.lineTo(x3,y3);
context.lineTo(x4,y4);
context.closePath();
context.fillStyle=color;
context.fill();
}
// context lines are always drawn half-in/half-out
// so context.lineWidth/2 is used a lot
var lw=this.lineWidth/2;
// shortcut vars for boundaries
var L=x-lw;
var R=x+lw;
var T=y-lw;
var B=y+lw;
// top
trapezoid(this,tColor, L,T, R+w,T, L+w,B, R,B );
// right
trapezoid(this,rColor, R+w,T, R+w,B+h, L+w,T+h, L+w,B );
// bottom
trapezoid(this,bColor, R+w,B+h, L,B+h, R,T+h, L+w,T+h );
// left
trapezoid(this,lColor, L,B+h, L,T, R,B, R,T+h );
// fill
this.fillStyle=fillColor;
this.fillRect(x,y,w,h);
// be kind -- always rewind (old vhs reference!)
this.restore();
// don't let this path leak
this.beginPath();
// chain
return(this);
};
// testing
ctx.lineWidth=20;
ctx.rainbowRect(100,50,100,50,"gold","red","blue","green","purple");
}); // end $(function(){});
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width=300 height=150></canvas>
Unfortunately, you can't draw borders of a rectangle just like you do with css.
You can use stroke() method, but it draws "borders" just with one color for every side.
So, I guess, you can draw borders manually by drawing lines near a rectangle.

mouse event won't fire on a kineticjs shape

Upgraded from 4.0.5 to 4.4.1 because Chrome stopped rendering correctly.
However, in the 4.0.5 version it was possible to draw a line in a Kinetic.Shape object and detect mouse events on it. This seems no longer the case. Even when using the recommended Canvas.fillStroke(this) call.
Here is some code:
var myshape = new Kinetic.Shape({
drawFunc: function(canvas) {
var context = canvas.getContext();
context.beginPath();
context.setLineWidth(20);
context.moveTo(100, 10);
context.lineTo(100, 60);
context.closePath();
context.stroke(); //this does
//canvas.fillStroke(this); //this doesn't bring the line on the screen
//context.fill(); //this doesn't make the event work either
context.beginPath();
context.setLineWidth(10);
context.moveTo(100, 60);
context.lineTo(100, 120);
context.closePath();
//canvas.fillStroke(this); //this doesn't bring the line on the screen
context.stroke(); //this does
canvas.fillStroke(this);
},
draggable: true
});
myshape.on('mousedown', function(event){
alert('mousedown');
});
An example in this fiddle: http://jsfiddle.net/GDQ6G/ (Only seems to render the line in Chrome. Not in firefox)
Another example here on this test page: http://www.planetinaction.com/linetest.htm
It is clear I am doing something wrong since this code doesn't render in Firefox. Can someone please show me this is done in the linked fiddle? The documentation for a shape shows how to draw a single item. I need to draw multiple items to form my custom shape as illustrated with this simplified two line example.
As it turns out based on Eric Rowells answer, a shape can only contain one path. That is a pity because version 4.0.5 was able to handle multiple paths until Google changed something funky in Chrome.
Anyway, the answer I was looking for is held in KineticJS groups. The code gets a lot more elaborate but it works.
var stage = new Kinetic.Stage({
container: 'container',
width: $('#container').width(),
height: $('#container').height()
});
var layer = new Kinetic.Layer('spline');
var group = new Kinetic.Group({
draggable: true,
});
group.add(new Kinetic.Shape({
drawFunc: function(canvas) {
var context = canvas.getContext();
context.beginPath();
context.moveTo(100, 10);
context.lineTo(100, 60);
context.closePath();
canvas.stroke(this);
},
strokeWidth: 6,
}));
group.add(new Kinetic.Shape({
drawFunc: function(canvas) {
var context = canvas.getContext();
context.beginPath();
context.moveTo(100, 60);
context.lineTo(100, 120);
context.closePath();
canvas.stroke(this);
},
strokeWidth: 20,
}));
group.on('mousedown', function(event){
alert('mousedown');
});
group.on('mouseover', function(event){
alert('mouseover');
});
layer.add(group);
stage.add(layer);
Here is the code in Fiddle: http://jsfiddle.net/QcsBH/
I could not find a reference in the documentation with regard to event handling by a group but I am pleasantly surprised to see that a group handles events of all the members in it.
Each KineticJS shape should only have one beginPath() and one closePath() execution. You also shouldn't ever directly stroke or fill using the context. You need to use the methods tied to the KineticJS canvas renderer:
canvas.stroke(this);
canvas.fill(this);
canvas.fillStroke(this);
Here's an example of drawing custom shapes:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-shape-tutorial/
If you bind a simple listener to the triangle in that tutorial, the event fires correctly (you can modify the code right on the page)

Resources