I've been trying to make a raphael animation, it's working fine and animating on load as it should but..
I want it to animate AGAIN when clicking a text or button (a div outsite raphael paper).
Raphael Code is here:
window.onload = function () {
var paper = new Raphael(document.getElementById('frivardihouse'), 250, 250);
function round(value, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
let ltv = 0.6;
let h = 144*ltv;
let y = 86+((1-ltv)*144);
let ltvtxt = round(ltv * 100);
var fillhouse = paper.rect(40.5,230,172.3,0).attr({fill: "#ff6600", stroke: "none"});
var sAnimation = Raphael.animation({ 'width': 172.3, 'height': h, 'x': 40.5, 'y': y, fill: "#ff0066"}, 2000, "backOut")
fillhouse.animate(sAnimation);
var thehouse = paper.path("M236.5,80.4L128.9,2.1c-1.6-1.1-3.7-1.1-5.3,0L16.1,80.4c-3.5,2.6-1.7,8.1,2.6,8.1l13,0c-1,2.5-1.5,5.3-1.5,8.2l0,122.7c0,12,9.2,21.7,20.6,21.7l150.9,0c11.4,0,20.6-9.7,20.6-21.7l0-122.7c0-2.9-0.5-5.7-1.5-8.2h13C238.2,88.6,240,83,236.5,80.4z M206.7,104.9l0,106.5c0,9-6.9,16.3-15.5,16.3l-129.9,0c-8.5,0-15.5-7.3-15.5-16.3l0-106.5c0-9,6.9-16.3,15.5-16.3l129.9,0C199.8,88.6,206.7,95.9,206.7,104.9z").attr({fill: "#ccc", stroke: "none"});
};
I made a fiddle.
I tried normal click functions, but just can't seem to get it to work :( Nothing is happening. Really frustrating!
See fiddle here
Just add the click event to .clickme class with jquery.
$('.clickme').click(function(){
document.getElementById('frivardihouse').innerHTML = '';
onload();
});
Below is the full code
onload = function () {
var paper = new Raphael(document.getElementById('frivardihouse'), 250, 250);
function round(value, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
let ltv = 0.6;
let h = 144*ltv;
let y = 86+((1-ltv)*144);
let ltvtxt = round(ltv * 100);
var fillhouse = paper.rect(40.5,230,172.3,0).attr({fill: "#ff6600", stroke: "none"});
var sAnimation = Raphael.animation({ 'width': 172.3, 'height': h, 'x': 40.5, 'y': y, fill: "#ff0066"}, 2000, "backOut")
fillhouse.animate(sAnimation);
var thehouse = paper.path("M236.5,80.4L128.9,2.1c-1.6-1.1-3.7-1.1-5.3,0L16.1,80.4c-3.5,2.6-1.7,8.1,2.6,8.1l13,0c-1,2.5-1.5,5.3-1.5,8.2l0,122.7c0,12,9.2,21.7,20.6,21.7l150.9,0c11.4,0,20.6-9.7,20.6-21.7l0-122.7c0-2.9-0.5-5.7-1.5-8.2h13C238.2,88.6,240,83,236.5,80.4z M206.7,104.9l0,106.5c0,9-6.9,16.3-15.5,16.3l-129.9,0c-8.5,0-15.5-7.3-15.5-16.3l0-106.5c0-9,6.9-16.3,15.5-16.3l129.9,0C199.8,88.6,206.7,95.9,206.7,104.9z").attr({fill: "#ccc", stroke: "none"});
};
$('.clickme').click(function(){
document.getElementById('frivardihouse').innerHTML = '';
onload();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="frivardihouse"></div>
<div class="clickme">
CLICK ME AND REPEAT ANIMATION
</div>
Without JQuery yes its possible!
Use dispatchEvent(new Event('load')); to trigger onload event from JS..
var clickEle = document.getElementsByClassName("clickme")[0];
clickEle.addEventListener("click", function() {
document.getElementById("frivardihouse").innerHTML = "";
dispatchEvent(new Event('load'));
});
onload = function() {
var paper = new Raphael(document.getElementById('frivardihouse'), 250, 250);
function round(value, precision) {
var multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
let ltv = 0.6;
let h = 144 * ltv;
let y = 86 + ((1 - ltv) * 144);
let ltvtxt = round(ltv * 100);
var fillhouse = paper.rect(40.5, 230, 172.3, 0).attr({
fill: "#ff6600",
stroke: "none"
});
var sAnimation = Raphael.animation({
'width': 172.3,
'height': h,
'x': 40.5,
'y': y,
fill: "#ff0066"
}, 2000, "backOut")
fillhouse.animate(sAnimation);
var thehouse = paper.path("M236.5,80.4L128.9,2.1c-1.6-1.1-3.7-1.1-5.3,0L16.1,80.4c-3.5,2.6-1.7,8.1,2.6,8.1l13,0c-1,2.5-1.5,5.3-1.5,8.2l0,122.7c0,12,9.2,21.7,20.6,21.7l150.9,0c11.4,0,20.6-9.7,20.6-21.7l0-122.7c0-2.9-0.5-5.7-1.5-8.2h13C238.2,88.6,240,83,236.5,80.4z M206.7,104.9l0,106.5c0,9-6.9,16.3-15.5,16.3l-129.9,0c-8.5,0-15.5-7.3-15.5-16.3l0-106.5c0-9,6.9-16.3,15.5-16.3l129.9,0C199.8,88.6,206.7,95.9,206.7,104.9z").attr({
fill: "#ccc",
stroke: "none"
});
};
//Trigger onload event while clicking the element
var clickEle = document.getElementsByClassName("clickme")[0];
clickEle.addEventListener("click", function() {
document.getElementById("frivardihouse").innerHTML = "";
dispatchEvent(new Event('load'));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<div id="frivardihouse"></div>
<div class="clickme">
CLICK ME AND REPEAT ANIMATION
</div>
The simplest way to do this is as follows
Call reFill function on the click event
function reFill() {
document.getElementById('frivardihouse').innerHTML = '';
onload();
}
That's it!.
if you are still looking for answer. I would solve a little bit different from the existing ones. Basically I would
not override onload by declaring onload function as global. it is a little bit difficult to follow
separate a method to draw and a method to animate.
in onload I will call draw and get an object back. this object will perform animation.
onClick will call animation method again
animation method will check if the house is already filled. if it is filled then remove the filled element only. Then it will performing to add new fill element with animation. fiddle link to check https://jsfiddle.net/hye0os7f/9/
function round(value, precision) {
const multiplier = Math.pow(10, precision || 0);
return Math.round(value * multiplier) / multiplier;
}
function draw() {
const paper = new Raphael(document.getElementById('frivardihouse'), 250, 250);
const ltv = 0.6;
const h = 144 * ltv;
const y = 86 + (1 - ltv) * 144;
const ltvtxt = round(ltv * 100);
const sAnimation = Raphael.animation(
{
width: 172.3,
height: h,
x: 40.5,
y,
fill: '#ff0066',
},
2000,
'backOut',
);
paper
.path(
'M236.5,80.4L128.9,2.1c-1.6-1.1-3.7-1.1-5.3,0L16.1,80.4c-3.5,2.6-1.7,8.1,2.6,8.1l13,0c-1,2.5-1.5,5.3-1.5,8.2l0,122.7c0,12,9.2,21.7,20.6,21.7l150.9,0c11.4,0,20.6-9.7,20.6-21.7l0-122.7c0-2.9-0.5-5.7-1.5-8.2h13C238.2,88.6,240,83,236.5,80.4z M206.7,104.9l0,106.5c0,9-6.9,16.3-15.5,16.3l-129.9,0c-8.5,0-15.5-7.3-15.5-16.3l0-106.5c0-9,6.9-16.3,15.5-16.3l129.9,0C199.8,88.6,206.7,95.9,206.7,104.9z',
)
.attr({ fill: '#ccc', stroke: 'none' });
let fillhouse = null;
return {
paper,
animateInHouse: () => {
if (fillhouse) {
fillhouse.remove();
}
fillhouse = paper.rect(40.5, 230, 172.3, 0).attr({ fill: '#ff6600', stroke: 'none' });
fillhouse.animate(sAnimation);
},
};
}
let thePaper = null;
const pageOnLoad = function () {
thePaper = draw();
thePaper.animateInHouse();
};
function onRedraw() {
thePaper.animateInHouse();
}
window.onload = pageOnLoad;
Related
I'm trying to achive a fluctuant animation effect using PaperJS.
I have a path cloned 2 times and each have 4 points, a setInterval animates every points in every path in a for cicle but for i>=2 i get this error "Cannot read property 'point' of undefined". Infact watching animation I can see that not all points are animated.
This is a codepen demo: https://codepen.io/mrk1977_02/pen/QWLgVMp?editors=0010
var path1;
var path2;
var path3;
var startX = 100;
var endX = 100;
var minValX = 30;
var maxValX = 30;
var minValY = 10;
var maxValY = 10;
var minVel = 3;
var maxVel = 3.5;
var interval = 1000;
var time = 2;
var segmentsArray = [[30, 92], [37, 45], [90, 50], [80, 120]];
var offsetVal1;
var offsetVal2;
var scaleArr = [1.4,1.3,1.2];
var ease = SlowMo.ease.config(0.1, 0.3, false);
//var ease = Power4.easeOut;
var circle_points1=null;
var circle_points2=null;
var circle_points3=null;
window.onload = function() {
paper.setup('myCanvas');
path1 = new Path({
segments: segmentsArray,
strokeColor: 'black',
strokeWidth: 0.1,
closed: true,
opacity:0
});
path1.fullySelected = false;
path1.position.x += startX;
path1.position.y += startX;
path1.smooth();
path1.simplify(1);
path1.scale(scaleArr[0]);
path2 = path1.clone();
//path2.smooth();
path2.scale(scaleArr[1]);
path3 = path2.clone();
//path3.smooth();
path3.scale(scaleArr[2]);
path1.fillColor = '#ececec';
path1.strokeWidth = 10;
path1.strokeColor = '#ddd';
setTimeout(initFadeIn, interval);
setInterval(shapeAnimation, interval);
}
function initFadeIn(){
var ease = Power4.easeOut;
TweenMax.to(path1, time, { opacity: 1, ease: ease, force3D: true});
TweenMax.to(path2, time, { opacity: 1, ease: ease, force3D: true});
TweenMax.to(path3, time, { opacity: 1, ease: ease, force3D: true});
}
function shapeAnimation(){
var timeShape = getRandomArbitrary(minVel, maxVel);
for(var i=0; i<segmentsArray.length; i++){
circle_points1 = path1.segments[i].point;
circle_points2 = path2.segments[i].point;
circle_points3 = path3.segments[i].point;
//console.log(path1.segments[i].point.x);
var minX = segmentsArray[i][0] - minValX;
var maxX = segmentsArray[i][0] + maxValX;
var minY = segmentsArray[i][1] - minValY;
var maxY = segmentsArray[i][1] + maxValY;
gapX = getRandomArbitrary(minX, maxX);
offsetValX = startX+gapX;
//console.log(offsetValX);
gapY = getRandomArbitrary(minY, maxY);
offsetValY = gapY+startX;
TweenMax.to(circle_points1, timeShape, { x: offsetValX, /*y: offsetValY,*/ ease: ease, force3D: true});
TweenMax.to(circle_points2, timeShape, { x: offsetValX, /*y: offsetValY,*/ ease: ease, force3D: true});
TweenMax.to(circle_points3, timeShape, { x: offsetValX, /*y: offsetValY,*/ ease: ease, force3D: true});
}
}
function getRandomArbitrary(min, max) {
var val = Math.round(Math.random() * (max - min) + min);
return val;
}
The problem comes from your call to path1.simplify(1);.
This actually removes some segments from your path. And later, when you try to iterate over 4 segments, the paths only contains 2.
So path1.segments[i] is undefined when i = 2 and path1.segments[i].point throws an error.
The solution is to remove the line path1.simplify(1);.
You already call path1.smooth(); which is enough in your case as it does the smoothing job and it won't remove any segment so your script will work as expected.
I am trying to make a quadratic curved arrow tool.
I was used demo in the following link for creating the tool.
http://kpomservices.com/oldweb/HTML5_Canvas_Curved_Lines.php
i was success to create an tool as I needs.
but I am getting some issues with that.
Hope someone here can help me on that issues...
here is the code I am using for creating the tool ..
line_number++;
var line;
var reinit_stroke = "";
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
function _getQBezierValue(t, p1, p2, p3) {
var iT = 1 - t;
return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
}
function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
return {
x: _getQBezierValue(position, startX, cpX, endX),
y: _getQBezierValue(position, startY, cpY, endY)
};
}
canvas.on({
'object:selected': onObjectSelected,
'object:moving': onObjectMoving,
'before:selection:cleared': onBeforeSelectionCleared
});
(function drawQuadratic() {
line = new fabric.Path('M 65 0 Q 100, 100, 200, 0', { fill: '', selectable: false,hasBorders: false,hasControls: false,stroke: #000});
line.path[0][1] = posx;
line.path[0][2] = posy;
line.path[1][1] = posx+50;
line.path[1][2] = posy+50;
line.path[1][3] = posx+100;
line.path[1][4] = posy+100;
line.id = line_number;
//line.selectable = false;
canvas.add(line);
canvas.sendBackwards(line);
var pt = getQuadraticCurvePoint(line.path[0][1], line.path[0][2], line.path[1][1], line.path[1][2], line.path[1][3], line.path[1][4], 0.5);
var p1 = makeCurvePoint(pt.x, pt.y, null, line, null)
p1.name = "p1";
p1.id = line_number;
canvas.add(p1);
var p0 = makeCurveCircle(posx, posy, line, p1, null);
p0.name = "p0";
p0.id = line_number;
canvas.add(p0);
var p2 = makeArrow(posx+100, posy+100, null, p1, line);
p2.name = "p2";
p2.id = line_number;
canvas.add(p2);
var dx = line.path[1][3] - posx;
var dy = line.path[1][4] - posy;
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
p2.setAngle(angle + 90);
p2.setCoords();
canvas.bringToFront(p2);
canvas.bringToFront(p0);
canvas.bringToFront(p1);
line.p2 = p2;
})();
function makeArrow(left, top, line1, line2, line3) {
var c = new fabric.Triangle({
width: 5,
height: 5,
left: left+5,
top: top+5,
strokeWidth: 10,
fill: #000,
opacity: 1,
stroke: #000
});
c.hasBorders = c.hasControls = false;
c.angle = 90;
c.line1 = line1;
c.line2 = line2;
c.line3 = line3;
return c;
}
function makeCurveCircle(left, top, line1, line2, line3) {
var c = new fabric.Circle({
radius: 5,
left: left,
top: top,
strokeWidth: 10,
fill: #000,
stroke: #000
});
c.hasBorders = c.hasControls = false;
c.line1 = line1;
c.line2 = line2;
c.line3 = line3;
return c;
}
function makeCurvePoint(left, top, line1, line2, line3) {
var c = new fabric.Circle({
radius: 5,
left: left,
top: top,
strokeWidth: 10,
fill: #000,
opacity: 0,
stroke: #000
});
c.hasBorders = c.hasControls = false;
c.angle = 90;
c.line1 = line1;
c.line2 = line2;
c.line3 = line3;
return c;
}
var prevselobj;
function onObjectSelected(e) {
var activeObject = e.target;
reinit_stroke = activeObject.stroke;
if (activeObject.name == "p0" || activeObject.name == "p2") {
if (prevselobj) {
prevselobj.line2.animate('opacity', '0', {
duration: 200,
onChange: canvas.renderAll.bind(canvas),
});
prevselobj.line2.selectable = false;
}
activeObject.line2.animate('opacity', '1', {
duration: 200,
onChange: canvas.renderAll.bind(canvas),
});
activeObject.line2.selectable = true;
prevselobj = activeObject;
}
}
function onBeforeSelectionCleared(e) {
var activeObject = e.target;
if (activeObject.name == "p0" || activeObject.name == "p2") {
activeObject.line2.animate('opacity', '0', {
duration: 200,
onChange: canvas.renderAll.bind(canvas),
});
activeObject.line2.selectable = false;
}
else if (activeObject.name == "p1") {
activeObject.animate('opacity', '0', {
duration: 200,
onChange: canvas.renderAll.bind(canvas),
});
activeObject.selectable = true;
}
}
function onObjectMoving(e) {
if (e.target.name == "p0" || e.target.name == "p2") {
var p = e.target;
var curvedline;
if (p.line1) {
p.line1.path[0][1] = p.left;
p.line1.path[0][2] = p.top + p.height/2;
curvedline = p.line1;
} else if (p.line3) {
p.line3.path[1][3] = p.left;
if(p.line3.path[0][2] <= p.line3.path[1][4])
p.line3.path[1][4] = p.top - p.height/2;
if(p.line3.path[0][2] > p.line3.path[1][4])
p.line3.path[1][4] = p.top + p.height/2;
p.line3.setCoords();
curvedline = p.line3;
}
if (curvedline) {
curvedline.setCoords();
var pt = getQuadraticCurvePoint(curvedline.path[0][1], curvedline.path[0][2], curvedline.path[1][1], curvedline.path[1][2], curvedline.path[1][3], curvedline.path[1][4], 0.5);
p.line2.left = pt.x;
p.line2.top = pt.y;
if (curvedline.p2) {
var pt = getQuadraticCurvePoint(curvedline.path[0][1], curvedline.path[0][2], curvedline.path[1][1], curvedline.path[1][2], curvedline.path[1][3], curvedline.path[1][4], 0.99);
curvedline.p2.left = pt.x;
curvedline.p2.top = pt.y;
var dx = curvedline.path[1][3] - pt.x;
var dy = curvedline.path[1][4] - pt.y;
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
curvedline.p2.setAngle(angle + 90);
curvedline.p2.setCoords();
}
p.line2.setCoords();
}
if (e.target.text) {
e.target.text.left = p.left;
e.target.text.top = p.top;
e.target.text.setCoords();
}
} else if (e.target.name == "p1") {
var p = e.target;
if (p.line2) {
p.line2.path[1][1] = p.left;
p.line2.path[1][2] = p.top;
}
curvedline = p.line2;
if (curvedline) {
var pt = getQuadraticCurvePoint(curvedline.path[0][1], curvedline.path[0][2], curvedline.path[1][1], curvedline.path[1][2], curvedline.path[1][3], curvedline.path[1][4], 0.5);
p.left = pt.x;
p.top = pt.y;
p.setCoords();
}
if (curvedline) {
var pt = getQuadraticCurvePoint(curvedline.path[0][1], curvedline.path[0][2], curvedline.path[1][1], curvedline.path[1][2], curvedline.path[1][3], curvedline.path[1][4], 0.99);
curvedline.p2.left = pt.x;
curvedline.p2.top = pt.y;
var dx = curvedline.path[1][3] - pt.x;
var dy = curvedline.path[1][4] - pt.y;
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
curvedline.p2.setAngle(angle + 90);
curvedline.p2.setCoords();
}
} else if (e.target.name == "p0" || e.target.name == "p2") {
var p = e.target;
p.line1 && p.line1.set({
'x2': p.left,
'y2': p.top
});
p.line2 && p.line2.set({
'x1': p.left,
'y1': p.top
});
p.line3 && p.line3.set({
'x1': p.left,
'y1': p.top
});
p.line4 && p.line4.set({
'x1': p.left,
'y1': p.top
});
}
p && reinit();
}
function reinit() {
canvas.remove(line);
line = new fabric.Path(line.path, { fill: '',selectable: false });
line.id = line_number;
line.stroke = reinit_stroke;
canvas.add(line);
canvas.sendBackwards(line);
}
canvas.on('mouse:over', function(e) {
if(e.target.type == "path")
{
canvas.sendBackwards(e.target);
}
});
I am using following code for delete the tool.
(Note: this code runs when I click on the delete button.
Delete button deletes the object selected. )
here is the code...
var objs = canvas.getObjects();
if (activeObject) {
active_id = activeObject.get('id');
canvas.forEachObject(function (obj) {
if(obj.get('id') == active_id)
{
if(obj.type == 'path')
{
obj.selectable = true;
canvas.remove(obj);
}
canvas.remove(obj);
}
});
canvas.remove(activeObject);
}
the issue I am getting is after delete this, If I create one other curved arrow in the designer(i.e. without reload the page) and move the nodes then its still showing the old path object which we already deleted.
and If I delete the newly created curved arrow then it removes all path from canvas, In short all Path object behave like one(Hope this is understandable) .
so, the path object is not getting deleted properly it is just getting hidden when I am deleting it. Or may be it is deleted but created again when I am moving another path object..
and if we save the canvas as json then we can see the path objects which are deleted too.
What I need to do is to delete the path object properly and save just the Path object currently showing on the canvas with nodes.
Please tell me if there is some solution for this issues..
Thanks..
I found the solution for the delete curve Issue.
We need to change the code at the reinit() functions.
It was overwrite the line_id to all the line(or say path) in the canvas.
Here is the code to replace with reinit() function..
function reinit() {
var objs = canvas.getObjects();
current_line = canvas.getActiveObject();
canvas.forEachObject(function (obj) {
if(obj.type == 'path' && obj.get("id") == current_line.get("id"))
{
canvas.remove(obj);
line = new fabric.Path(obj.path, { fill: '',selectable: false });
line.id = current_line.get("id");
line.stroke = reinit_stroke;
canvas.add(line);
canvas.sendBackwards(line);
}
});
}
Hope this can be helpful for someone who need this kind of solution..
I am trying to change this paperscript:
<script type="text/paperscript" canvas="canvas-1">
tool.minDistance = 10;
tool.maxDistance = 45;
var path;
function onMouseDown(event) {
path = new Path();
path.fillColor = new Color({ hue: Math.random() * 360, saturation: 1, brightness: 1 });
path.add(event.point);
}
function onMouseDrag(event) {
var step = event.delta / 2;
step.angle += 90;
var top = event.middlePoint + step;
var bottom = event.middlePoint - step;
path.add(top);
path.insert(0, bottom);
path.smooth();
}
function onMouseUp(event) {
path.add(event.point);
path.closed = true;
path.smooth();
}
</script>
to a stand alone javascript like:
paper.install(window);
window.onload = function() {
paper.setup('myCanvas');
tool.minDistance = 10;
tool.maxDistance = 45;
var path;
function onMouseDown(event) {
path = new Path();
path.fillColor = {
hue: Math.random() * 360,
saturation: 1,
brightness: 1
};
path.add(event.point);
}
function onMouseDrag(event) {
var step = event.delta / 2;
step.angle += 90;
var top = event.middlePoint + step;
var bottom = event.middlePoint - step;
path.add(top);
path.insert(0, bottom);
path.smooth();
}
function onMouseUp(event) {
path.add(event.point);
path.closed = true;
path.smooth();
}
}
it give me an error:
TypeError: undefined is not an object (evaluating 'tool.minDistance =
10')
What is tool here? I understand that I might need to declare it before I can use it. Any idea how to resolve this?
You need to make the global scope as outlined in the documentation :
paper.install(window);
Then get on with global defs. :
window.onload = function() {
// Get a reference to the canvas object
paper.setup('myCanvas');
// In your case create tools
var tool = new Tool();
tool.minDistance = 10;
tool.maxDistance = 45;
Then continue as usual, this will set up your tools.. More can be found here.
Incidentally you've actually already done this correctly for Path(), so the same applies to Tool()
When I use Paper.js directly in javascript I prefer to create paper object this way:
var canvas = document.getElementById('canvas-line');
paper.setup(canvas);
// and then if you want to create some Paper.js object prefix it's name with paper
var myPath = new paper.Path();
If you want to use tool you need to decelerate it with new paper.Tool();
For example if you want to check whether path was clicked:
var tool1 = new paper.Tool();
var handle;
var myPath;
myPath.fullySelected = true;
tool1.onMouseDown = function(event) {
handle = null;
// Do a hit test on path for handles:
var hitResult = myPath.hitTest(event.point, {
handles: true,
fill: true,
stroke: true,
segments: true,
tolerance: 2
});
if (hitResult) {
if (hitResult.type == 'handle-in') {
handle = hitResult.segment.handleIn;
} else if (hitResult.type == 'segment') {
handle = hitResult.segment.point;
} else if (hitResult.type == 'handle-out') {
handle = hitResult.segment.handleOut;
}
}
}
You can find more informations about tools in here http://paperjs.org/reference/tool/
I am trying to make a sticky note type utility with the fabric canvas. It will help to be used as annotators.
I want the text to wrap by itself at the given rectangle's width.
Can someone update my fiddle work??
Suggestions are appreciated. Regards...
The following is the link to a part of my fiddle:
http://jsfiddle.net/U7E9q/5/
var canvas = new fabric.Canvas('fabric-canvas');
canvas.hoverCursor = 'pointer';
var text = new fabric.IText("Enter Text Here ",{
fontSize: 20,
top: 100,
left: 100,
backgroundColor: '#faa',
lockScalingX: true,
lockScalingY: true,
selectable: true
});
//alert(text.text);
var rect = new fabric.Rect({
text_field: text,
width: 200,
height: 50,
fill: '#faa',
rx: 10,
ry: 10,
top: 100,
left: 100
});
canvas.add(rect);
canvas.add(text);
canvas.on('object:moving', function (event){
canvas.renderAll();
});
createListenersKeyboard();
function createListenersKeyboard() {
document.onkeydown = onKeyDownHandler;
//document.onkeyup = onKeyUpHandler;
}
function onKeyDownHandler(event) {
//event.preventDefault();
var key;
if(window.event){
key = window.event.keyCode;
}
else{
key = event.keyCode;
}
switch(key){
//////////////
// Shortcuts
//////////////
// Copy (Ctrl+C)
case 67: // Ctrl+C
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
copy();
}
}
break;
// Delete (Ctrl+D)
case 127: // Ctrl+D
if(ableToShortcut()){
if(event.deleteKey){
delet();
}
}
break;
// Paste (Ctrl+V)
case 86: // Ctrl+V
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
paste();
}
}
break;
default:
// TODO
break;
}
}
function ableToShortcut(){
/*
TODO check all cases for this
if($("textarea").is(":focus")){
return false;
}
if($(":text").is(":focus")){
return false;
}
*/
return true;
}
function copy(){
if(canvas.getActiveGroup()){
for(var i in canvas.getActiveGroup().objects){
var object = fabric.util.object.clone(canvas.getActiveGroup().objects[i]);
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObjects[i] = object;
}
}
else if(canvas.getActiveObject()){
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObject = object;
copiedObjects = new Array();
}
}
function paste(){
if(copiedObjects.length > 0){
for(var i in copiedObjects){
canvas.add(copiedObjects[i]);
}
}
else if(copiedObject){
canvas.add(copiedObject);
}
canvas.renderAll();
}
function delet(){
var activeObject = canvas.getActiveObject();
canvas.remove(activeObject);
console.log('after remove getActiveObject(): ', canvas.getActiveObject(), activeObject === canvas.getActiveObject());
canvas.renderAll();
}
If you manage the sticky note as a grouped rect and text you can improve the same behavior. When you need to edit the text inside the group, you just ungroup and clone the elements, append the cloned elements to the canvas and set text as editable.
You need to handle an event like double click to handle this behavior and then handle the mousedown or other interactivity with canvas to regroup them.
http://jsfiddle.net/4HE3U/1/
Above is one fiddle that can satisfy you
Basically i have made one group of Text and Rectangle and i have added it to canvas. There is only one change you need to make is that you can take one textbox to get current sticky note text content as we can not edit text of i-text online once we are adding it any group. Currently there is no way for IText to handle the events as they are not handed down to it if it's contained in a group. I think this is also the prefered way to handle that as it would confuse the user - what if he starts to edit multiple texts. This might end up in a mess. Maybe you can rework your script a little to workaround this problems.
I have added Text and Rectangle
var canvas = new fabric.Canvas('fabric-canvas');
canvas.hoverCursor = 'pointer';
var text = new fabric.IText("Enter Text Here ",{
fontSize: 20,
top: 100,
left: 100,
backgroundColor: '#faa',
lockScalingX: true,
lockScalingY: true,
selectable: true
});
//alert(text.text);
var rect = new fabric.Rect({
text_field: text,
width: 200,
height: 50,
fill: '#faa',
rx: 10,
ry: 10,
top: 100,
left: 100
});
var group = new fabric.Group([ rect, text ], {
left: 100,
top: 100,
lockScalingX: true,
lockScalingY: true,
hasRotatingPoint: false,
transparentCorners: false,
cornerSize: 7
});
canvas.add(group);
//canvas.add(text);
canvas.on('object:moving', function (event){
canvas.renderAll();
});
createListenersKeyboard();
function createListenersKeyboard() {
document.onkeydown = onKeyDownHandler;
//document.onkeyup = onKeyUpHandler;
}
function onKeyDownHandler(event) {
//event.preventDefault();
var key;
if(window.event){
key = window.event.keyCode;
}
else{
key = event.keyCode;
}
switch(key){
//////////////
// Shortcuts
//////////////
// Copy (Ctrl+C)
case 67: // Ctrl+C
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
copy();
}
}
break;
// Delete (Ctrl+D)
case 127: // Ctrl+D
if(ableToShortcut()){
if(event.deleteKey){
delet();
}
}
break;
// Paste (Ctrl+V)
case 86: // Ctrl+V
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
paste();
}
}
break;
default:
// TODO
break;
}
}
function ableToShortcut(){
/*
TODO check all cases for this
if($("textarea").is(":focus")){
return false;
}
if($(":text").is(":focus")){
return false;
}
*/
return true;
}
function copy(){
if(canvas.getActiveGroup()){
for(var i in canvas.getActiveGroup().objects){
var object = fabric.util.object.clone(canvas.getActiveGroup().objects[i]);
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObjects[i] = object;
}
}
else if(canvas.getActiveObject()){
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObject = object;
copiedObjects = new Array();
}
}
function paste(){
if(copiedObjects.length > 0){
for(var i in copiedObjects){
canvas.add(copiedObjects[i]);
}
}
else if(copiedObject){
canvas.add(copiedObject);
}
canvas.renderAll();
}
function delet(){
var activeObject = canvas.getActiveObject();
canvas.remove(activeObject);
console.log('after remove getActiveObject(): ', canvas.getActiveObject(), activeObject === canvas.getActiveObject());
canvas.renderAll();
}
<canvas id="fabric-canvas" width="400" height="400"></canvas>
Here is Sticky note functionality. Text wrap working and font size changes w.r.t sticky note width and height. Editing mode activates on double click.
export const createStickyNotes = (canvas, options) => {
fabric.StickyNote = fabric.util.createClass(fabric.Group, {
type: "StickyNote",
initialize: function (options) {
this.set(options);
var height = this.height;
var width = this.width;
this.rectObj = new fabric.Rect({
width: width,
height: height,
fill: this.rectObj?.fill ?? "rgba(251,201,112,1)",
originX: "center",
originY: "center",
objectCaching: false,
stateProperties: ["fill"],
});
this.textObj = new fabric.Textbox(this.textObj?.text ?? "Notes", {
originX: "center",
originY: "center",
textAlign: "center",
width: 100,
hasControls: false,
fontSize: this.textObj?.fontSize ?? 30,
lineHeight: 1,
stateProperties: ["text", "fontSize"],
scaleX: this.textObj?.scaleX ?? 1,
scaleY: this.textObj?.scaleY ?? 1,
objectCaching: false,
breakWords: true,
fontFamily: "Open Sans",
});
this._objects = [this.rectObj, this.textObj];
// this custom _set function will set custom properties value to object when it will load from json.
// at that time loadFromJson function will call this initialize function.
// this._setCustomProperties(this.options);
canvas.renderAll();
//evenet will fire if the object is double clicked by mouse
this.on("mousedblclick", (e) => {
var pasteFlag = false;
var scaling = e.target.getScaledWidth() / 100;
var textForEditing;
canvas.bringToFront(e.target);
e.target.selectable = false;
const [rectObj, textObj] = this.getObjects();
textObj.clone(function (clonedObj) {
clonedObj.set({
left: e.target.left,
top: e.target.top,
lockMovementY: true,
lockMovementX: true,
hasBorders: false,
scaleX: scaling,
scaleY: scaling,
breakWords: true,
width: textObj.width,
stateProperties: [],
});
textForEditing = clonedObj;
});
this.remove(textObj);
canvas.add(textForEditing);
canvas.setActiveObject(textForEditing);
textForEditing.enterEditing();
textForEditing.selectAll();
textForEditing.paste = (function (paste) {
return function (e) {
disableScrolling();
pasteFlag = true;
};
})(textForEditing.paste);
textForEditing.on("changed", function (e) {
var fontSize = textForEditing.fontSize;
var charCount = Math.max(textForEditing._text.length, 1);
var charWR =
(textForEditing.textLines.length * width) / (charCount * fontSize);
if (textForEditing.height < height - 15) {
fontSize = Math.min(
Math.sqrt(
((height - 10 - fontSize) / 1.16) *
(width / (charCount * charWR))
),
30
);
}
if (textForEditing.height > height - 15) {
fontSize = Math.sqrt(
((height - 10) / 1.16) * (width / (charCount * charWR))
);
}
if (pasteFlag) {
pasteFlag = false;
while (
textForEditing.height > height - 15 &&
textForEditing.fontSize > 0
) {
fontSize = textForEditing.fontSize -= 0.2;
canvas.renderAll();
}
}
textForEditing.fontSize = fontSize;
});
textForEditing.on("editing:exited", () => {
enableScrolling();
canvas.setActiveObject(textObj);
textObj.set({
text: textForEditing.text,
fontSize: textForEditing.fontSize,
visible: true,
});
this.add(textObj);
this.selectable = true;
canvas.remove(textForEditing);
canvas.discardActiveObject();
});
});
function disableScrolling() {
var x = window.scrollX;
var y = window.scrollY;
window.onscroll = function () {
window.scrollTo(x, y);
};
}
var _wrapLine = function (_line, lineIndex, desiredWidth, reservedSpace) {
var lineWidth = 0,
splitByGrapheme = this.splitByGrapheme,
graphemeLines = [],
line = [],
// spaces in different languges?
words = splitByGrapheme
? fabric.util.string.graphemeSplit(_line)
: _line.split(this._wordJoiners),
word = "",
offset = 0,
infix = splitByGrapheme ? "" : " ",
wordWidth = 0,
infixWidth = 0,
largestWordWidth = 0,
lineJustStarted = true,
additionalSpace = splitByGrapheme ? 0 : this._getWidthOfCharSpacing();
reservedSpace = reservedSpace || 0;
desiredWidth -= reservedSpace;
for (var i = 0; i < words.length; i++) {
// i would avoid resplitting the graphemes
word = fabric.util.string.graphemeSplit(words[i]);
wordWidth = this._measureWord(word, lineIndex, offset);
offset += word.length;
// Break the line if a word is wider than the set width
if (this.breakWords && wordWidth >= desiredWidth) {
if (!lineJustStarted) {
graphemeLines.push(line);
line = [];
lineWidth = 0;
lineJustStarted = true;
}
this.fontSize *= desiredWidth / (wordWidth + 1);
// Loop through each character in word
for (var w = 0; w < word.length; w++) {
var letter = word[w];
var letterWidth =
(this.getMeasuringContext().measureText(letter).width *
this.fontSize) /
this.CACHE_FONT_SIZE;
line.push(letter);
lineWidth += letterWidth;
}
word = [];
} else {
lineWidth += infixWidth + wordWidth - additionalSpace;
}
if (lineWidth >= desiredWidth && !lineJustStarted) {
graphemeLines.push(line);
line = [];
lineWidth = wordWidth;
lineJustStarted = true;
} else {
lineWidth += additionalSpace;
}
if (!lineJustStarted) {
line.push(infix);
}
line = line.concat(word);
infixWidth = this._measureWord([infix], lineIndex, offset);
offset++;
lineJustStarted = false;
// keep track of largest word
if (wordWidth > largestWordWidth && !this.breakWords) {
largestWordWidth = wordWidth;
}
}
i && graphemeLines.push(line);
if (largestWordWidth + reservedSpace > this.dynamicMinWidth) {
this.dynamicMinWidth =
largestWordWidth - additionalSpace + reservedSpace;
}
return graphemeLines;
};
fabric.util.object.extend(fabric.Textbox.prototype, {
_wrapLine: _wrapLine,
});
function enableScrolling() {
window.onscroll = function () {};
}
},
toObject: function (propertiesToInclude) {
// This function is used for serialize this object. (used for create json)
// not inlclude this.textObj and this.rectObj into json because when object will load from json, init fucntion of this class is called and it will assign this two object textObj and rectObj again.
var obj = this.callSuper(
"toObject",
[
"objectCaching",
"textObj",
"rectObj",
// ... property list that you want to add into json when this object is convert into json using toJSON() function. (serialize)
].concat(propertiesToInclude)
);
// delete objects array from json because then object load from json, Init function will call. which will automatically re-assign object and assign _object array.
delete obj.objects;
return obj;
},
});
fabric.StickyNote.async = true;
fabric.StickyNote.fromObject = function (object, callback) {
// This function is used for deserialize json and convert object json into button object again. (called when we call loadFromJson() fucntion on canvas)
return fabric.Object._fromObject("StickyNote", object, callback);
};
return new fabric.StickyNote(options);
};
//How to use
var options = {
width: 100,
height: 100,
originX: "center",
originY: "center",
};
var notes = StickyNotes(canvas, options);
canvas.add(notes);
I am trying to recreate the game http://www.sinuousgame.com/ and started studying html5 canvas and kineticJS.
Recently i came across the getIntersection function and coudnt find much details regarding it.But with what i had ,i did make a code to get the Collision detection done using getIntersection() function.
But it doesnt seem to be working.
As you can see, My Fiddle: http://jsfiddle.net/p9fnq/8/
//The working player code
var LimitedArray = function(upperLimit) {
var storage = [];
// default limit on length if none/invalid supplied;
upperLimit = +upperLimit > 0 ? upperLimit : 100;
this.push = function(item) {
storage.push(item);
if (storage.length > upperLimit) {
storage.shift();
}
return storage.length;
};
this.get = function(flag) {
return storage[flag];
};
this.iterateItems = function(iterator) {
var flag, l = storage.length;
if (typeof iterator !== 'function') {
return;
}
for (flag = 0; flag < l; flag++) {
iterator(storage[flag]);
}
};
};
var tail = new LimitedArray(50);
var flag = 0, jincr = 0;
var stage = new Kinetic.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
listening: true
});
var layer = new Kinetic.Layer({
listening: true
});
stage.add(layer);
var player = new Kinetic.Circle({
x: 20,
y: 20,
radius: 6,
fill: 'cyan',
stroke: 'black',
draggable: true
});
var line = new Kinetic.Line({
points: [],
stroke: 'cyan',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round'
});
layer.add(line);
layer.add(player);
// move the circle with the mouse
stage.getContent().addEventListener('mousemove', function() {
player.position(stage.getPointerPosition());
var obj = {
x: stage.getPointerPosition().x,
y: stage.getPointerPosition().y
};
tail.push(obj);
var arr = [];
tail.iterateItems(function(p) {
arr.push(p.x, p.y);
});
line.points(arr);
});
var x = 0;
var y = 0;
var noOfEnemies = 200;
var enemyArmada = new Array();
createEnemy();
function createEnemy() {
for (var i = 0; i < noOfEnemies; i++) {
var enemy = new Kinetic.Circle({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: 4.5 + 1.5 * Math.random(),
fill: 'red',
stroke: 'black'
});
enemy.speedX = enemy.speedY = (0.5 + Math.random() * 50);
enemyArmada.push(enemy);
layer.add(enemy);
}
}
var checkCollide = function() {
var position = stage.getPointerPosition();
if(position == null)
position = player.position();
if(position == null)
position = {x:0,y:0};
var collided = stage.getIntersection(position);
console.log(position);
if (typeof collided !== 'Kinetic.Shape') {
console.log("not shape");
}
else {
console.log("BOOOM!!!");
}
};
var anim = new Kinetic.Animation(function(frame) {
checkCollide();
for (var i = 0; i < noOfEnemies; i++) {
var e = enemyArmada[i];
e.position({
x: e.position().x - e.speedX * (frame.timeDiff / 400),
y: e.position().y + e.speedY * (frame.timeDiff / 400)
});
if (e.position().y < 0 || e.position().x < 0) {
e.position({
x: (Math.random() * (window.innerWidth + 600)),
y: -(Math.random() * window.innerHeight)
});
}
}
}, layer);
anim.start();
I need the collision to be detected. The function i have written here is checkCollide and its called within the kinetic.Animation function.
Can anyone help me out with this??
(If you don't know the solution,please do like the post,i need the solution badly)
The source of the problem
getIntersection(point) means "is any object at this point".
Since the point you're using is the player's position, getIntersection will always return true because player is always at its own position !
One solution
Put your player on one layer and all enemies on a separate layer.
That way you can hit test the enemy layer without the interference of the player object.
Code and a Demo: http://jsfiddle.net/m1erickson/JCfW8/
<!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:350px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var enemyLayer = new Kinetic.Layer();
stage.add(enemyLayer);
var playerLayer = new Kinetic.Layer();
stage.add(playerLayer);
var player = new Kinetic.Circle({
x:100,
y:100,
radius: 10,
fill: 'green',
draggable: true
});
player.on("dragmove",function(){
if(enemyLayer.getIntersection(player.position())){
this.fill("red");
playerLayer.draw();
}
});
playerLayer.add(player);
playerLayer.draw();
var enemy = new Kinetic.Circle({
x:200,
y:100,
radius: 20,
fill: 'blue',
draggable: true
});
enemyLayer.add(enemy);
enemyLayer.draw();
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag the green player<br>Player will turn red if it collides<br>with the blue enemy</h4>
<div id="container"></div>
</body>
</html>
Another solution
Mathematically test the player against every enemy:
Warning: untested code--some tweaking might be required
function playerEnemyCollide(){
var playerX=player.x();
var playerY=player.y();
var playerRadius=player.radius();
for(var i=0;i<enemyArmada.length;i++){
var e=enemyArmada[i];
if(circlesColliding(playerX,playerY,playerRadius,e.x,e.y,e.radius)){
return(true);
}
}
return(false);
}
function circlesColliding(cx1,cy1,radius1,cx2,cy2,radius2){
var dx=cx2-cx1;
var dy=cy2-cy1;
return(dx*dx+dy*dy<(radius1*2+radius2*2);
}