fabricjs: _render method of my fabric.Object subclass is never called - render

The initialize method is called but render method is not.
I read about subclassing on the fabricjs website and looked at that demo.
I really don't understand what is missing in my code.
var CustomCircle = fabric.util.createClass(fabric.Object, {
type: "customCircle",
initialize: function (options) {
this.callSuper('initialize', options);
},
_render: function (ctx) {
ctx.beginPath();
ctx.arc(100, 250, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = '#003300';
ctx.stroke();
}
});
var customCircle = new CustomCircle();
canvas.add(customCircle);
Here is a fiddle.

When you want to create a custom object it is up to you to calculate and set the width and height of this latest. To do so you can call this.set({width: ..., height: ...}) inside the initialize function.
var canvas = new fabric.Canvas('c');
var CustomCircle = fabric.util.createClass(fabric.Object, {
radius: 50,
type: "customCircle",
initialize: function (options) {
this.callSuper('initialize', options);
this.set({ width: 2 * this.radius, height: 2 * this.radius });
},
_render: function (ctx) {
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = '#003300';
ctx.stroke();
}
});
var customCircle = new CustomCircle({left:50, top:50});
canvas.add(customCircle);
See the fiddle
Also note that inside the _render function, the canvas has already been translated to the center of your custom object.

Related

HTML5 Canvas How To Fill A Mouse Drawn Triangle

I am trying to fill a triangle shape on a HTML5 canvas drawn by dragging the mouse.
I have similar effect for circles, rectangles working.
The code showing both the working drawCircle and not working drawTriangle functions is below. The outline of the triangle gets drawn but it is not filled. I have tried loving the context.stroke line to various places in the sequence of there is no effect.
<style>
#divContainer {
width: 100%;
height: 80%;
background: #ddd;
}
#divContentArea {
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
}
.canvas {
cursor: crosshair;
position:relative;
left:0px;
top:0px;
}
</style>
<div>
Click the button to select the shape type then click and drag mouse on the canvas below.
<BR>
<button type="button" onClick='shapetype="circle";'>Draw Circle</button>
<button type="button" onClick='shapetype="triangle";'>Draw Triangle</button>
<BR>
</div>
<div id="divContainer">
<div id="divContentArea">
<canvas id="canvas" class='canvas'>
Sorry, your browser does not support a canvas object.
</canvas>
</div>
</div>
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var canrect = canvas.getBoundingClientRect();
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var lastPoint;
var startPoint;
var isDrawing = false;
var shapetype = 'triangle';
canvas.onmousedown = function(e) {
isDrawing = true;
if ( shapetype == 'circle' ) {
canvas.removeEventListener("mousemove", drawTriangle, false);
canvas.addEventListener("mousemove", drawCircle, false);
} else {
canvas.removeEventListener("mousemove", drawCircle, false);
canvas.addEventListener("mousemove", drawTriangle, false);
}
lastPoint = { x: e.offsetX, y: e.offsetY };
startPoint = lastPoint;
};
function drawTriangle(e) {
// This doesn't work - triangle is not filled
e.preventDefault();
e.stopPropagation();
if (!isDrawing) return;
mx = e.offsetX;
my = e.offsetY;
// clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// calculate the rectangle width/height based
// on starting vs current mouse position
var twidth = Math.abs(mx - startPoint.x) ;
var theight = Math.abs(my - startPoint.y) ;
// draw a new rect from the start position
// to the current mouse position
context.beginPath();
context.lineWidth = 3;
context.lineJoin = context.lineCap = 'round';
context.setLineDash([0, 0]);
context.globalAlpha = 1.0;
if ( mx >= startPoint.x ) {
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx, my);
context.moveTo(mx-(2*twidth), my );
context.lineTo(mx, my);
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx-(2*twidth), my );
} else {
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx, my);
context.moveTo(mx+(2*twidth), my );
context.lineTo(mx, my);
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx+(2*twidth), my );
}
context.closePath();
context.strokeStyle = 'red';
context.stroke();
context.fillStyle = 'rgba(25,50,75,0.5)';
context.fill();
}
function drawCircle(e) {
// This works
e.preventDefault();
e.stopPropagation();
if (!isDrawing) return;
mx = e.offsetX;
my = e.offsetY;
// clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// calculate the rectangle width/height based
// on starting vs current mouse position
var cradius = Math.abs(mx - startPoint.x) ;
// draw a new rect from the start position
// to the current mouse position
context.beginPath();
context.lineWidth = 3;
context.lineJoin = context.lineCap = 'round';
context.setLineDash([0, 0]);
context.globalAlpha = 1.0;
context.strokeStyle = 'red';
context.arc(startPoint.x, startPoint.y, cradius, 0, 2 * Math.PI, false);
context.fillStyle = 'rgba(25,50,75,0.5)';
context.fill();
context.stroke();
}
canvas.onmouseup = function() {
isDrawing = false;
};
canvas.onmouseleave = function() {
isDrawing = false;
};
</script>
function drawTriangle(e) {
e.preventDefault();
e.stopPropagation();
if (!isDrawing) return;
// clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// draw a new rect from the start position
// to the current mouse position
context.strokeStyle = 'red';
context.fillStyle = 'rgba(25,50,75,0.5)';
context.lineWidth = 3;
context.lineJoin = context.lineCap = 'round';
context.setLineDash([0, 0]);
context.globalAlpha = 1.0;
context.beginPath();
context.moveTo(startPoint.x, startPoint.y);
context.lineTo(e.offsetX, e.offsetY);
context.lineTo(startPoint.x * 2 - e.offsetX, e.offsetY);
context.closePath();
context.stroke();
context.fill();
}
The CanvasRenderingContext2D's fill() method fills a path with a given color. To be able to fill such a path, it must contain at least three points - which is fulfilled in your case.
The problem is the way you're creating the path:
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx, my);
context.moveTo(mx-(2*twidth), my );
context.lineTo(mx, my);
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx-(2*twidth), my );
By calling moveTo() again, you're essentially starting a new path - thus you have just three single lines hence nothing to fill.
Try making the path in one go:
context.moveTo(startPoint.x, startPoint.y );
context.lineTo(mx, my);
context.lineTo(mx-(2*twidth), my );
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var canrect = canvas.getBoundingClientRect();
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var lastPoint;
var startPoint;
var isDrawing = false;
var shapetype = 'triangle';
canvas.onmousedown = function(e) {
isDrawing = true;
if (shapetype == 'circle') {
canvas.removeEventListener("mousemove", drawTriangle, false);
canvas.addEventListener("mousemove", drawCircle, false);
} else {
canvas.removeEventListener("mousemove", drawCircle, false);
canvas.addEventListener("mousemove", drawTriangle, false);
}
lastPoint = {
x: e.offsetX,
y: e.offsetY
};
startPoint = lastPoint;
};
function drawTriangle(e) {
// This doesn't work - triangle is not filled
e.preventDefault();
e.stopPropagation();
if (!isDrawing) return;
mx = e.offsetX;
my = e.offsetY;
// clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// calculate the rectangle width/height based
// on starting vs current mouse position
var twidth = Math.abs(mx - startPoint.x);
var theight = Math.abs(my - startPoint.y);
// draw a new rect from the start position
// to the current mouse position
context.beginPath();
context.lineWidth = 3;
context.lineJoin = context.lineCap = 'round';
context.setLineDash([0, 0]);
context.globalAlpha = 1.0;
if (mx >= startPoint.x) {
context.moveTo(startPoint.x, startPoint.y);
context.lineTo(mx, my);
context.lineTo(mx - (2 * twidth), my);
} else {
context.moveTo(startPoint.x, startPoint.y);
context.lineTo(mx, my);
context.lineTo(mx + (2 * twidth), my);
}
context.closePath();
context.strokeStyle = 'red';
context.stroke();
context.fillStyle = 'rgba(25,50,75,0.5)';
context.fill();
}
function drawCircle(e) {
// This works
e.preventDefault();
e.stopPropagation();
if (!isDrawing) return;
mx = e.offsetX;
my = e.offsetY;
// clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// calculate the rectangle width/height based
// on starting vs current mouse position
var cradius = Math.abs(mx - startPoint.x);
// draw a new rect from the start position
// to the current mouse position
context.beginPath();
context.lineWidth = 3;
context.lineJoin = context.lineCap = 'round';
context.setLineDash([0, 0]);
context.globalAlpha = 1.0;
context.strokeStyle = 'red';
context.arc(startPoint.x, startPoint.y, cradius, 0, 2 * Math.PI, false);
context.fillStyle = 'rgba(25,50,75,0.5)';
context.fill();
context.stroke();
}
canvas.onmouseup = function() {
isDrawing = false;
};
canvas.onmouseleave = function() {
isDrawing = false;
};
#divContainer {
width: 100%;
height: 80%;
background: #ddd;
}
#divContentArea {
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
}
.canvas {
cursor: crosshair;
position: relative;
left: 0px;
top: 0px;
}
<div>
Click the button to select the shape type then click and drag mouse on the canvas below.
<BR>
<button type="button" onClick='shapetype="circle";'>Draw Circle</button>
<button type="button" onClick='shapetype="triangle";'>Draw Triangle</button>
<BR>
</div>
<div id="divContainer">
<div id="divContentArea">
<canvas id="canvas" class='canvas'>
Sorry, your browser does not support a canvas object.
</canvas>
</div>
</div>

Loading image in Canvas with mouse follow

I am struggling to make an image appear in a Canvas animated card. I can get text and drawn images to show up, but I can't seem to load the image and still have the ball follow the mouse. Why is that? I'm sure it's something super easy but I keep trying different things and am getting no where!
<script>
var canvas = document.querySelector("#myCanvas");
var context = canvas.getContext("2d");
var canvasPos = getPosition(canvas);
var mouseX = 525;
var mouseY = 325;
canvas.addEventListener("mousemove", setMousePosition, false);
function setMousePosition(e) {
mouseX = e.clientX - canvasPos.x;
mouseY = e.clientY - canvasPos.y;
}
function getPosition(el) {
var xPosition = 0;
var yPosition = 0;
while (el) {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPosition += (el.offsetTop - el.scrollTop + el.clientTop);
el = el.offsetParent;
}
return {
x: xPosition,
y: yPosition
};
}
function loadTeacher() {
var myImage = new Image();
myImage.src = 'images/teacher.jpg';
myImage.addEventListener("load", loadImage, false);
function loadImage(e) {
context.drawImage(myImage, 0, 75, 500, 350);
}
} loadTeacher();
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
//write text
context.font = "bold 12pt Helvetica, Ariel, sans-serif";
context.textAlign = "center";
context.fillStyle = "black";
context.fillText("This teacher needs an apple now! Drag the apple to her mouth.", 250, 50);
//draw circle
context.beginPath();
context.arc(mouseX, mouseY, 15, 0, 2 * Math.PI, true);
context.fillStyle = "red";
context.fill();
requestAnimationFrame(update);
}
update();
</script>

How to resize text on canvas

I have found a way to be able to resize and squeeze my text on normal HTML5 Canvas with the following code:
$("input").on("input", function() {
ctx.clearRect(0, 0, 400, 400);
var width = ctx.measureText(text).width;
if(width <= 100) {
ctx.fillText(text, 0, 100);
} else {
ctx.save();
ctx.scale(100 / width, 1);
ctx.fillText(text, 0, 100);
ctx.restore();
}
});
I want to do the same thing but with using the FabricJS. Is there a way that I can do it?
Yeah sure, just check the width of the text element after you've created it and set the ScaleX property if needed before adding it to the canvas:
var canvas = new fabric.Canvas('c');
var t;
canvas.renderAll();
$("input").on("input", function () {
if (t) {
canvas.remove(t);
}
var textToDraw = $(this).val();
t = new fabric.Text(textToDraw, {
left: 0,
top: 0
});
if (t.width > c.width) {
console.log('scale -> ' + (c.width / t.width));
t.setScaleX(c.width / t.width);
}
canvas.add(t);
});
If you wish to do this on object resize, use the modified event handler like so:
function scaleText() {
console.log("scale=" + t.getScaleX() + " w=" + t.getWidth());
if ((t.width * t.getScaleX()) > c.width) {
t.setScaleX(c.width / t.width);
}
}
$("input").on("input", function () {
if (t) {
canvas.remove(t);
}
var textToDraw = $(this).val();
t = new fabric.Text(textToDraw, {
left: 0,
top: 0
});
scaleText();
t.on("modified", function (options) {
scaleText();
});
canvas.add(t);
});

Can't Detect Mouseover on This KineticJS Shape?

I have a KineticJS shape that draws a bezier curve that is wider on one end. It draws correctly, but I can't yet detect a 'mouseover' event on it. I have created a small JSFiddle demo of the anomaly, at:
http://jsfiddle.net/VikR0001/nZYxL/6/
How can I detect 'mouseover' events on this shape?
Thanks very much in advance to all for any info!
var mainLayer;
//bezier curve code:
//http://stackoverflow.com/questions/8325680/how-to-draw-a-bezier-curve-with-variable-thickness-on-an-html-canvas
//draw a bezier curve that gets larger as it flows
//adapted for use with KineticJS
function drawBezierCurve() {
var centerLeft = new Object();
centerLeft.x = 100;
centerLeft.y = 400;
var centerRight = new Object();
centerRight.x = 400;
centerRight.y = 100;
var thicknessLeft = 1;
var thicknessRight = 50;
var color = "#000";
var context = mainLayer.getContext();
var leftUpper = {
x: centerLeft.x,
y: centerLeft.y - thicknessLeft / 2
};
var leftLower = {
x: centerLeft.x,
y: leftUpper.y + thicknessLeft
};
var rightUpper = {
x: centerRight.x,
y: centerRight.y - thicknessRight / 2
};
var rightLower = {
x: centerRight.x,
y: rightUpper.y + thicknessRight
};
var center = (centerRight.x + centerLeft.x) / 2;
var cp1Upper = {
x: center,
y: leftUpper.y
};
var cp2Upper = {
x: center,
y: rightUpper.y
};
var cp1Lower = {
x: center,
y: rightLower.y
};
var cp2Lower = {
x: center,
y: leftLower.y
};
var bezierCurve = new Kinetic.Shape({
drawFunc: function (canvas) {
var context = mainLayer.getContext();
context.fillStyle = color;
context.beginPath();
context.moveTo(leftUpper.x, leftUpper.y);
context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
context.lineTo(rightLower.x, rightLower.y);
context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
context.lineTo(leftUpper.x, leftUpper.y);
context.fill();
canvas.stroke(this);
},
fill: color,
stroke: color,
strokeWidth: 1
});
bezierCurve.on('mouseover', function (evt) {
document.body.style.cursor = "pointer";
$("#debug").html("MOUSEOVER DETECTED."); //<==NEVER CALLED
});
bezierCurve.on('mouseout', function (evt) {
document.body.style.cursor = "default";
$("#debug").html("MOUSEOUT DETECTED."); //NEVER CALLED
});
bezierCurve.setAttrs({
'leftUpper': leftUpper,
'leftLower': leftLower,
'rightUpper': rightUpper,
'rightLower': rightLower,
'cp1Upper': cp1Upper,
'cp2Upper': cp2Upper,
'cp1Lower': cp1Lower,
'cp2Lower': cp2Lower
});
mainLayer.add(bezierCurve);
mainLayer.draw();
$("#debug").html("bezier curve has been drawn onscreen.");
}
$(document).ready(function () {
var stage = new Kinetic.Stage({
container: 'canvasContainer',
width: 500,
height: 500
});
mainLayer = new Kinetic.Layer('main');
stage.add(mainLayer);
mainLayer.draw();
drawBezierCurve();
});
Can you define it as an SVG element, and just give that an onmouseover?
Fixed it! Changes are shown at the jsFiddle link in the original post.
//FIXED!
//OLD VERSION: DOES NOT WORK
// var bezierCurve = new Kinetic.Shape({
// drawFunc: function (canvas) {
// var context = mainLayer.getContext();
// context.fillStyle = color;
// context.beginPath();
// context.moveTo(leftUpper.x, leftUpper.y);
// context.bezierCurveTo(cp1Upper.x, cp1Upper.y, cp2Upper.x, cp2Upper.y, rightUpper.x, rightUpper.y);
// context.lineTo(rightLower.x, rightLower.y);
// context.bezierCurveTo(cp1Lower.x, cp1Lower.y, cp2Lower.x, cp2Lower.y, leftLower.x, leftLower.y);
// context.lineTo(leftUpper.x, leftUpper.y);
// context.closePath();
// context.fill();
// canvas.stroke(this);
// },
// fill: color,
// stroke: color,
// strokeWidth: 1
// });
//NEW VERSION: WORKS!
var bezierCurve = new Kinetic.Shape({
drawFunc: function (canvas) {
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(leftUpper.x, leftUpper.y);
context.bezierCurveTo(cp1Upper.x,cp1Upper.y, cp2Upper.x,cp2Upper.y, rightUpper.x,rightUpper.y);
context.lineTo(rightLower.x, rightLower.y);
context.bezierCurveTo(cp1Lower.x,cp1Lower.y, cp2Lower.x,cp2Lower.y, leftLower.x,leftLower.y);
context.lineTo(leftUpper.x, leftUpper.y);
context.fill();
canvas.stroke(this);
},
fill: color,
stroke: color,
strokeWidth: 3
});

KineticJS mouseover circle - cursor style

I am using the following to change the style of the cursor when the mouse is over the circle:
circle1.on('mouseover', function () {
document.body.style.cursor = 'pointer';
});
circle1.on('mouseout', function () {
document.body.style.cursor = 'default';
});
It works great if I draw the circle using:
var circle1 = new Kinetic.Circle({
x: 512,
y: 512,
radius: 140,
stroke: '#00ffff',
strokeWidth: 4,
opacity: 0.5
});
However if I use:
var circle1 = new Kinetic.Circle({
drawFunc: function (canvas) {
var context1 = canvas.getContext();
context1.beginPath();
context1.arc(512, 512, this.getRadius(), 0, 2 * Math.PI, false);
context1.lineWidth = this.getStrokeWidth();
context1.strokeStyle = this.getStroke();
context1.stroke();
},
radius: 140,
stroke: '#00ffff',
strokeWidth: 15,
opacity: 0.5
});
It does not work! The cursor does not change its style; can we just use radius for mouse over. I would appreciate your suggestions, thanks in advance.
As I know you also need to define "drawHitFunc":
circle1.setDrawHitFunc(function (canvas) {
var context2 = canvas.getContext();
context2.beginPath();
context2.arc(100, 100, this.getRadius(), 0, 2 * Math.PI, false);
context2.closePath();
canvas.fillStroke(this);
});
Example: http://jsfiddle.net/lavrton/4DJdU/1/
no, you just need to correctly structure the drawFunc when creating custom shapes. Here's an example:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-shape-tutorial/
The problem is that you're using context.stroke(). You need to use canvas.stroke(this);
Anytime you actually render something, like strokes and fills, you need to use the canvas renderer object because it draws onto both the scene graph (what you see) and a specialized hit graph (used for event detection)
Docs:
http://kineticjs.com/docs/symbols/Kinetic.Canvas.php

Resources