Related
So I'm currently working on a project that requires similar functionality to what you would expect in a floorplan designing application, and I need to be able to essentially draw a polygon (room) by connecting points generated on-click, with lines (walls).
I've found a few really great examples that resemble the functionality I'm looking for, but I'm unsure of how to approach implementing it as a functional react component.
I've seen Fabric.js and Konva.js used in a few other implementations of this kind of functionality, but I can't seem to be able to find a react example to use as a reference..
Below is an example I found on codepen using jQuery
var min = 99;
var max = 999999;
var polygonMode = true;
var pointArray = new Array();
var lineArray = new Array();
var activeLine;
var activeShape = false;
var canvas
$(window).load(function() {
prototypefabric.initCanvas();
$('#create-polygon').click(function() {
prototypefabric.polygon.drawPolygon();
});
});
var prototypefabric = new function() {
this.initCanvas = function() {
canvas = window._canvas = new fabric.Canvas('c');
canvas.setWidth($(window).width());
canvas.setHeight($(window).height() - $('#nav-bar').height());
//canvas.selection = false;
canvas.on('mouse:down', function(options) {
if (options.target && options.target.id == pointArray[0].id) {
prototypefabric.polygon.generatePolygon(pointArray);
}
if (polygonMode) {
prototypefabric.polygon.addPoint(options);
}
});
canvas.on('mouse:up', function(options) {
});
canvas.on('mouse:move', function(options) {
if (activeLine && activeLine.class == "line") {
var pointer = canvas.getPointer(options.e);
activeLine.set({
x2: pointer.x,
y2: pointer.y
});
var points = activeShape.get("points");
points[pointArray.length] = {
x: pointer.x,
y: pointer.y
}
activeShape.set({
points: points
});
canvas.renderAll();
}
canvas.renderAll();
});
};
};
prototypefabric.polygon = {
drawPolygon: function() {
polygonMode = true;
pointArray = new Array();
lineArray = new Array();
activeLine;
},
addPoint: function(options) {
var random = Math.floor(Math.random() * (max - min + 1)) + min;
var id = new Date().getTime() + random;
var circle = new fabric.Circle({
radius: 5,
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 0.5,
left: (options.e.layerX / canvas.getZoom()),
top: (options.e.layerY / canvas.getZoom()),
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
id: id,
objectCaching: false
});
if (pointArray.length == 0) {
circle.set({
fill: 'red'
})
}
var points = [(options.e.layerX / canvas.getZoom()), (options.e.layerY / canvas.getZoom()), (options.e.layerX / canvas.getZoom()), (options.e.layerY / canvas.getZoom())];
line = new fabric.Line(points, {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
class: 'line',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false
});
if (activeShape) {
var pos = canvas.getPointer(options.e);
var points = activeShape.get("points");
points.push({
x: pos.x,
y: pos.y
});
var polygon = new fabric.Polygon(points, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false
});
canvas.remove(activeShape);
canvas.add(polygon);
activeShape = polygon;
canvas.renderAll();
} else {
var polyPoint = [{
x: (options.e.layerX / canvas.getZoom()),
y: (options.e.layerY / canvas.getZoom())
}];
var polygon = new fabric.Polygon(polyPoint, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false
});
activeShape = polygon;
canvas.add(polygon);
}
activeLine = line;
pointArray.push(circle);
lineArray.push(line);
canvas.add(line);
canvas.add(circle);
canvas.selection = false;
},
generatePolygon: function(pointArray) {
var points = new Array();
$.each(pointArray, function(index, point) {
points.push({
x: point.left,
y: point.top
});
canvas.remove(point);
});
$.each(lineArray, function(index, line) {
canvas.remove(line);
});
canvas.remove(activeShape).remove(activeLine);
var polygon = new fabric.Polygon(points, {
stroke: '#333333',
strokeWidth: 0.5,
fill: 'red',
opacity: 1,
hasBorders: false,
hasControls: false
});
canvas.add(polygon);
activeLine = null;
activeShape = null;
polygonMode = false;
canvas.selection = true;
}
};
* {
font-family: "Roboto", sans-serif;
font-weight: 100;
}
body {
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" rel="stylesheet" />
<nav class="grey darken-4" role="navigation" id="nav-bar" style="height:50px;">
<div class="nav-wrapper container">
<a id="logo-container" class="brand-logo" style="line-height: 50px;font-size: 20px;">Fabric Polygon</a>
<ul id="nav-mobile" class="right hide-on-med-and-down">
<li><a id="create-polygon" style="line-height: 50px;font-size: 20px;cursor:pointer;">Create Polygon</a></li>
</ul>
</div>
</nav>
<div class="section no-pad-bot no-pad-top">
<canvas id="c"></canvas>
</div>
And this is essentially the end functionality I'd like to achieve.
I really appreciate any help or advice!
To begin with, you should keep in mind that react is meant for structuring the visual components of your application and their interaction with the rest of your code, that is to say react is not a framework that covers everything you may need.
I'm not even sure that react is what you need, maybe you need a canvas. Maybe the canvas resides in a react component.
Nevertheless, if you decide that you would like to go at it with react then lets break down what you need, visually:
a "canvas" component which contains all your polygons or lines
a "polygon" which contains lines
a "line" component.
to begin with, before the interaction, make sure you create a state that makes sense that will be sent to the canvas, maybe an array of polygon or line objects. a line object is clearly two points, maybe a color, etc.
If you got this far with a mock of your polgyon state, its time to add a state manager (like redux) and some event listeners to update the state when events happen in the DOM (like mouse or keyboard events.)
To summarize:
Use react for visual elements.
Use redux to manage the state of the canvas.
When initializing the parent component (the "canvas"), create event listeners that fire actions that affect the state.
I am working at a project with fabric js. I tried to minimize my problem, so I'm hoping that the code isn't too messed up.
I am creating some Objects which are linked with each other:
A Line, which contains a Start and an Endpoint
A Circle, which is StartPoint of 1 line and Endpoint of another line
with this combination i can create different shapes(like a polygon) and modify my move-functions for them too.
When a Circle is dragged, the related Lines are scaling and moving too. (in my code you can move the lines too and the shape is resized after that, but i didnt put it into this example, bc this short extract should be enough to show what my problem is.)
I got a little example in jsfiddle: https://jsfiddle.net/bxgox7cr/
When you look at the ends of the lines, you can clearly see a cut, so the eye soon recognize, that this is not a connected shape but rather some lines which are close to each other. Is there a way to modify the look of the lines, that the shape looks "closed"?
Here is my code, i tried to put some comments, that it is easy to read:
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
/** ------------creating a Line Object, which contains a start and an endpoint ------------**/
fabric.LineWithPoints = fabric.util.createClass(fabric.Line, {
initialize: function(points, options) {
options || (options = {});
this.callSuper('initialize', points, options);
options &&
this.set('type', options.type),
this.set('name', options.name),
this.set('start_point', options.start_point),
this.set('end_point', options.end_point),
this.set('current_x', options.current_x),
this.set('current_y', options.current_y)
},
setStartPointAndEndPoint: function(start_point, end_point) {
this.set({
start_point: start_point,
end_point: end_point
});
},
setValues: function(new_x1, new_y1, new_x2, new_y2) {
// console.log(this);
this.set({
x1: new_x1,
x2: new_x2,
y1: new_y1,
y2: new_y2
});
this.setCoords();
}
});
/**--- modifie the circle element, adding new functions for the movement of the object-------*/
fabric.LinePoint = fabric.util.createClass(fabric.Circle, {
initialize: function(options) {
options || (options = {});
this.callSuper('initialize', options);
options &&
this.set('subtype', 'line_point'),
this.set('x', this.left),
this.set('y', this.top)
},
setPointCoordinates: function(new_left, new_top) {
this.set({
x: new_left,
y: new_top,
left: new_left,
top: new_top
});
this.setCoords();
},
move: function(new_left, new_top) {
var wall_1 = this.line1;
var wall_2 = this.line2;
this.setPointCoordinates(new_left, new_top);
wall_1.setValues(wall_1.x1, wall_1.y1, this.getLeft(), this.getTop());
wall_2.setValues(this.getLeft(), this.getTop(), wall_2.x2, wall_2.y2);
canvas.renderAll();
},
});
/**------------------- Moving Function------------------------------------------------- */
canvas.on('object:moving', function(event) {
var object = event.target;
if (object.subtype == "line_point") {
object.move(object.getLeft(), object.getTop());
}
});
/**------------------------------ create functions for the objects -----------------------*/
function newCircleObject(left, top, wall_1, wall_2) {
var circle = new fabric.LinePoint({
left: left,
top: top,
strokeWidth: 2,
radius: 15,
fill: 'grey',
stroke: 'black',
opacity: 0.1,
perPixelTargetFind: true,
subtype: 'line_point',
includeDefaultValues: false
});
circle.hasControls = false;
circle.hasBorders = false;
circle.line1 = wall_1;
circle.line2 = wall_2;
return circle;
}
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
/**------------------------------ adding the shapes--------------------------------*/
var wall_1 = newWallObject([100, 100, 100, 500]);
var wall_2 = newWallObject([100, 500, 500, 500]);
var wall_3 = newWallObject([500, 500, 500, 100]);
var wall_4 = newWallObject([500, 100, 100, 100]);
var end_point_1 = newCircleObject(wall_1.x1, wall_1.y1, wall_4, wall_1);
var end_point_2 = newCircleObject(wall_2.x1, wall_2.y1, wall_1, wall_2);
var end_point_3 = newCircleObject(wall_3.x1, wall_3.y1, wall_2, wall_3);
var end_point_4 = newCircleObject(wall_4.x1, wall_4.y1, wall_3, wall_4);
wall_1.setStartPointAndEndPoint(end_point_1.name, end_point_2.name);
wall_2.setStartPointAndEndPoint(end_point_2.name, end_point_3.name);
wall_3.setStartPointAndEndPoint(end_point_3.name, end_point_4.name);
wall_4.setStartPointAndEndPoint(end_point_4.name, end_point_1.name);
canvas.add(wall_1, wall_2, wall_3, wall_4, end_point_1, end_point_2, end_point_3, end_point_4);
Add strokeLineCap: 'round',:
function newWallObject(coords) {
var wall = new fabric.LineWithPoints(coords, {
stroke: 'black',
strokeWidth: 6,
lockScalingX: true,
lockScalingY: true,
perPixelTargetFind: true,
strokeLineCap: 'round',
subtype: 'line',
type: 'line',
padding: 10,
includeDefaultValues: false
});
wall.hasControls = false;
wall.hasBorders = false;
return wall;
}
I looked up: http://fabricjs.com/docs/fabric.Object.html#strokeLineCap
My moveMask function in the code below is called x amount of times for each HTML element with the class .layout-box.
I'm struggling to think of a way of re-writing my code so that it only runs the functions once, each time an event listener is invoked.
I understand this might not be a simple fix, but if I could get some some idea of the direction I need to be heading in, then I'd really appreciate it.
// Store SVG container element as variable
var layoutBox = document.querySelectorAll(".layout-box"),
xPos = 0,
yPos = 0,
elOver = true,
clicked = false,
rect,
coords,
split = document.querySelectorAll('.words h2, .words h3, .words p, .words li');
// GSAP tweens/functions that require access to coordinates
function moveMask(valueX, valueY) {
[].slice.call(layoutBox).forEach(function(el, i) {
console.log("test");
var img = el.querySelectorAll('.img');
// Mask move animation
var tlMove = new TimelineMax({
paused: true
});
tlMove.set(img, {
webkitClipPath: '40px at ' + valueX + 'px ' + valueY + 'px'
});
el.animationMaskMove = tlMove;
// Mask click animation
var tlClick = tlLeave = new TimelineMax({
paused: true
});
tlClick.to(img, 0.8, {
webkitClipPath: '500px at ' + valueX + 'px ' + valueY + 'px',
});
tlClick.to(img, 0.4, {
'-webkit-filter': 'grayscale(0%)',
filter: 'grayscale(0%)'
}, '-=0.8');
el.animationMaskClick = tlClick;
tlClick.progress(1).progress(0); // Forces an initial render of this tween so that it's cached for its 2nd usage
// Mask leave animation
var tlLeave = new TimelineMax({
paused: true
});
tlLeave.to(img, 0.2, {
webkitClipPath: '0 at ' + valueX + 'px ' + valueY + 'px',
'-webkit-filter': 'grayscale(100%)',
filter: 'grayscale(100%)'
});
el.animationMaskLeave = tlLeave;
tlLeave.progress(1).progress(0);
// Mask slow leave animation
var tlLeaveSlow = new TimelineMax({
paused: true
});
tlLeaveSlow.to(img, 0.6, {
ease: Bounce.easeOut,
webkitClipPath: '0 at ' + valueX + 'px ' + valueY + 'px',
'-webkit-filter': 'grayscale(100%)',
filter: 'grayscale(100%)'
});
el.animationMaskSlowLeave = tlLeaveSlow;
tlLeaveSlow.progress(1).progress(0);
});
}
// Force initial run with temporary coordinates (so tweens can be cached)
moveMask(-100, -100);
// GSAP tweens/functions that don't access to coordinates
[].slice.call(layoutBox).forEach(function(el, i) {
console.log("test2");
var svgContainer = el.querySelectorAll('.svg-container');
var svgContainerLeft = el.querySelectorAll('.left');
var svgContainerBottom = el.querySelectorAll('.bottom');
var img = el.querySelectorAll('.img');
var mask = el.querySelectorAll('.mask');
var text = el.querySelectorAll('.text');
var elSplit = el.querySelectorAll('.words');
// Container 3d push effect
var tlContainer3d = new TimelineMax({
paused: true
});
tlContainer3d.to(svgContainer, 0.3, {
x: "-=6px",
y: "+=6px"
});
el.animationContainer3d = tlContainer3d;
tlContainer3d.progress(1).progress(0);
// Container 3d push effect left
var tlContainer3dLeft = new TimelineMax({
paused: true
});
tlContainer3dLeft.to(svgContainerLeft, 0.3, {
width: "4px"
});
el.animationContainer3dLeft = tlContainer3dLeft;
tlContainer3dLeft.progress(1).progress(0);
// Container 3d push effect bottom
var tlContainer3dBottom = new TimelineMax({
paused: true
});
tlContainer3dBottom.to(svgContainerBottom, 0.3, {
height: "4px",
right: "+=6px"
});
el.animationContainer3dBottom = tlContainer3dBottom;
tlContainer3dBottom.progress(1).progress(0);
// Img move animation
var tlImgMove = new TimelineMax({
paused: true
});
tlImgMove.to(img, 0.3, {
scale: 1,
ease: Sine.easeOut,
'-webkit-filter': 'grayscale(0%)',
filter: 'grayscale(0%)'
}, 0.05);
el.animationImgMove = tlImgMove;
tlImgMove.progress(1).progress(0);
// Mask click text animation
var tlText = new TimelineMax({
paused: true
})
tlText.to(elSplit, .3, {
autoAlpha: 0
});
el.animationTextClick = tlText;
tlText.progress(1).progress(0);
});
// Loop through boxes and assign event listeners
for (var x = 0; x < layoutBox.length; x++) {
layoutBox[x].addEventListener("mousemove", function(e) {
if (elOver) { // Only run if mousedown hasn't been triggered
// Get coordinates of container
rect = this.getBoundingClientRect();
xPos = e.pageX - rect.left;
yPos = e.pageY - rect.top - window.scrollY;
// Add coordinates to array and pass in to moveMask function
coords = [xPos, yPos];
moveMask.apply(null, coords);
this.animationMaskMove.play();
}
});
layoutBox[x].addEventListener("mousedown", function(e) {
this.animationContainer3d.play();
this.animationContainer3dLeft.play();
this.animationContainer3dBottom.play();
this.animationMaskClick.play();
this.animationTextClick.play();
clicked = true;
elOver = false;
});
layoutBox[x].addEventListener("mouseleave", function(e) {
this.animationContainer3d.reverse();
this.animationContainer3dLeft.reverse();
this.animationContainer3dBottom.reverse();
this.animationTextClick.timeScale(1.25).reverse();
// If clicked then run slow animation
if (clicked) {
this.animationMaskSlowLeave.play();
} else {
this.animationMaskLeave.play();
}
clicked = false;
elOver = true;
});
}
Edit:
// Store SVG container element as variable
var layoutBox = document.querySelectorAll(".layout-box"),
elOver = true,
clicked = false,
xPos = 0,
yPos = 0,
rect,
coords;
// GSAP tweens/functions that require access to coordinates
function moveMask(el, valueX, valueY) {
console.log("test");
var img = el.querySelectorAll('.img');
// Mask move animation
var tlMove = new TimelineMax({
paused: true
});
tlMove.set(img, {
webkitClipPath: '40px at ' + valueX + 'px ' + valueY + 'px'
});
el.animationMaskMove = tlMove;
// Mask click animation
var tlClick = tlLeave = new TimelineMax({
paused: true
});
tlClick.to(img, 0.8, {
webkitClipPath: '500px at ' + valueX + 'px ' + valueY + 'px',
});
tlClick.to(img, 0.4, {
'-webkit-filter': 'grayscale(0%)',
filter: 'grayscale(0%)'
}, '-=0.8');
el.animationMaskClick = tlClick;
tlClick.progress(1).progress(0); // Forces an initial render of this tween so that it's cached for its 2nd usage
// Mask leave animation
var tlLeave = new TimelineMax({
paused: true
});
tlLeave.to(img, 0.2, {
webkitClipPath: '0 at ' + valueX + 'px ' + valueY + 'px',
'-webkit-filter': 'grayscale(100%)',
filter: 'grayscale(100%)'
});
el.animationMaskLeave = tlLeave;
tlLeave.progress(1).progress(0);
// Mask slow leave animation
var tlLeaveSlow = new TimelineMax({
paused: true
});
tlLeaveSlow.to(img, 0.6, {
ease: Bounce.easeOut,
webkitClipPath: '0 at ' + valueX + 'px ' + valueY + 'px',
'-webkit-filter': 'grayscale(100%)',
filter: 'grayscale(100%)'
});
el.animationMaskSlowLeave = tlLeaveSlow;
tlLeaveSlow.progress(1).progress(0);
}
// Force initial run with temporary coordinates (so tweens can be cached)
[].forEach.call(layoutBox, function(el, i) { moveMask(el, -100, -100) });
// GSAP tweens/functions that don't access to coordinates
[].forEach.call(layoutBox, function(el, i) {
console.log("test2");
var svgContainer = el.querySelectorAll('.svg-container');
var svgContainerLeft = el.querySelectorAll('.left');
var svgContainerBottom = el.querySelectorAll('.bottom');
var img = el.querySelectorAll('.img');
var mask = el.querySelectorAll('.mask');
var text = el.querySelectorAll('.text');
var elSplit = el.querySelectorAll('.words');
// Container 3d push effect
var tlContainer3d = new TimelineMax({
paused: true
});
tlContainer3d.to(svgContainer, 0.3, {
x: "-=6px",
y: "+=6px"
});
el.animationContainer3d = tlContainer3d;
tlContainer3d.progress(1).progress(0);
// Container 3d push effect left
var tlContainer3dLeft = new TimelineMax({
paused: true
});
tlContainer3dLeft.to(svgContainerLeft, 0.3, {
width: "4px"
});
el.animationContainer3dLeft = tlContainer3dLeft;
tlContainer3dLeft.progress(1).progress(0);
// Container 3d push effect bottom
var tlContainer3dBottom = new TimelineMax({
paused: true
});
tlContainer3dBottom.to(svgContainerBottom, 0.3, {
height: "4px",
right: "+=6px"
});
el.animationContainer3dBottom = tlContainer3dBottom;
tlContainer3dBottom.progress(1).progress(0);
// Img move animation
var tlImgMove = new TimelineMax({
paused: true
});
tlImgMove.to(img, 0.3, {
scale: 1,
ease: Sine.easeOut,
'-webkit-filter': 'grayscale(0%)',
filter: 'grayscale(0%)'
}, 0.05);
el.animationImgMove = tlImgMove;
tlImgMove.progress(1).progress(0);
// Mask click text animation
var tlText = new TimelineMax({
paused: true
})
tlText.to(elSplit, .3, {
autoAlpha: 0
});
el.animationTextClick = tlText;
tlText.progress(1).progress(0);
});
// Event listener functions
function myMouseMove(e) {
if (elOver) { // Only run if mousedown hasn't been triggered
// Get coordinates of container
rect = this.getBoundingClientRect();
xPos = e.pageX - rect.left;
yPos = e.pageY - rect.top - window.scrollY;
// Add coordinates to array and pass in to moveMask function
coords = [xPos, yPos];
moveMask.call(null, coords);
this.animationMaskMove.play();
}
}
function myMouseDown(e) {
this.animationContainer3d.play();
this.animationContainer3dLeft.play();
this.animationContainer3dBottom.play();
this.animationMaskClick.play();
this.animationTextClick.play();
clicked = true;
elOver = false;
};
function myMouseLeave(e) {
this.animationContainer3d.reverse();
this.animationContainer3dLeft.reverse();
this.animationContainer3dBottom.reverse();
this.animationTextClick.timeScale(1.25).reverse();
// If clicked then run slow animation
if (clicked) {
this.animationMaskSlowLeave.play();
} else {
this.animationMaskLeave.play();
}
clicked = false;
elOver = true;
};
// Loop through boxes and assign event listener
for (var x = 0; x < layoutBox.length; x++) {
layoutBox[x].addEventListener("mousemove", myMouseMove);
//layoutBox[x].addEventListener("mousedown", this, myMouseDown);
//layoutBox[x].addEventListener("mouseleave", this, myMouseLeave);
};
When clicked, I want a button to produce three child options, which then when tapped again should do retract and then disappear when behind the parent button. I hope this is clear from the code (note this is a nativescript application, hence the slightly strange css choices).
exports.fabTap = function (args) {
var google = page.getViewById("google");
var facebook = page.getViewById("facebook");
var amazon = page.getViewById("amazon");
if (clicked == false) {
google.style.visibility = "visible";
facebook.style.visibility = "visible";
amazon.style.visibility = "visible";
google.animate({
translate: { x: -55, y: -66 },
duration: 500,
curve: enums.AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
facebook.animate({
translate: { x: 0, y: -75 },
duration: 500,
curve: enums.AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
amazon.animate({
translate: { x: 55, y: -66 },
duration: 500,
curve: enums.AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
} else {
google.animate({
translate: { x: 0, y: 0 },
duration: 500,
curve: enums.AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
facebook.animate({
translate: { x: 0, y: 0 },
duration: 500,
curve: enums.AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
amazon.animate({
translate: { x: 0, y: 0 },
duration: 500,
curve: enums.AnimationCurve.cubicBezier(0.1, 0.1, 0.1, 1)
});
google.style.visibility = "collapse";
facebook.style.visibility = "collapse";
amazon.style.visibility = "collapse";
}
clicked = !clicked;
}
However, as you can see from the gif, on the return journey, the buttons just disappear before the return. What can I do to ensure this animates in sequence?
You're doing this:
google.style.visibility = "collapse";
facebook.style.visibility = "collapse";
amazon.style.visibility = "collapse";
immediately after starting the animations, not giving the animations time to run before you do it.
If you want to wait, either use a callback on the animations, or just delay 500ms.
I don't know what animation lib you're using, so I can't show an example of a callback waiting for all three to be done.
Here's the "just wait 500ms" version, though:
setTimeout(function() {
google.style.visibility = "collapse";
facebook.style.visibility = "collapse";
amazon.style.visibility = "collapse";
}, 500);
You could probably do this easily with transitions. Instead of using visibility, try using opacity to fade your buttons in and out. Or, you could add a transition end listener to your JS and only set visibility to collapse after the three tabs have moved back under the main button. I think your problem is trying to animate visibility.
I am creating a little app with KineticJS which creates multiple nodes (RegularPolygons). After the stage is loaded (triggered with play(); ) I fill each node sequentially with an (pattern)image (triggered with loadImages(urls);.
This works fine, but now I want to add a nice fade-in effect with a Tween, like:
set all nodes > load single image > set node patternImage > tween to opacity 0.8 > on tween complete, load next image (repeat).
For some reason the tweens won't play, they just appear in complete state (opacity 1) ;(
var stage = new Kinetic.Stage({
container: 'canvas',
width: $(window).width(),
height: $(window).height()
});
var layer = new Kinetic.Layer()
var layer2 = new Kinetic.Layer()
var urls =
[
'http://lorempixel.com/300/300/sports/1',
'http://lorempixel.com/300/300/sports/2',
'http://lorempixel.com/300/300/sports/3',
'http://lorempixel.com/300/300/sports/4',
'http://lorempixel.com/300/300/sports/5',
'http://lorempixel.com/300/300/sports/6',
'http://lorempixel.com/300/300/sports/7',
'http://lorempixel.com/300/300/sports/8',
'http://lorempixel.com/300/300/sports/9',
'http://lorempixel.com/300/300/sports/10',
'http://lorempixel.com/300/300/business/1',
'http://lorempixel.com/300/300/business/2',
'http://lorempixel.com/300/300/business/3',
'http://lorempixel.com/300/300/business/4',
'http://lorempixel.com/300/300/business/5',
'http://lorempixel.com/300/300/business/6',
'http://lorempixel.com/300/300/business/7',
'http://lorempixel.com/300/300/business/8',
'http://lorempixel.com/300/300/business/9',
'http://lorempixel.com/300/300/business/10'/*,
'http://lorempixel.com/300/300/cats/1',
'http://lorempixel.com/300/300/cats/2',
'http://lorempixel.com/300/300/cats/3',
'http://lorempixel.com/300/300/cats/4',
'http://lorempixel.com/300/300/cats/5',
'http://lorempixel.com/300/300/cats/6',
'http://lorempixel.com/300/300/cats/7',
'http://lorempixel.com/300/300/cats/8',
'http://lorempixel.com/300/300/cats/9',
'http://lorempixel.com/300/300/cats/10',
'http://lorempixel.com/300/300/nature/1',
'http://lorempixel.com/300/300/nature/2',
'http://lorempixel.com/300/300/nature/3',
'http://lorempixel.com/300/300/nature/4',
'http://lorempixel.com/300/300/nature/5',
'http://lorempixel.com/300/300/nature/6',
'http://lorempixel.com/300/300/nature/7',
'http://lorempixel.com/300/300/nature/8',
'http://lorempixel.com/300/300/nature/9',
'http://lorempixel.com/300/300/nature/10',
'http://lorempixel.com/300/300/people/1',
'http://lorempixel.com/300/300/people/2',
'http://lorempixel.com/300/300/people/3',
'http://lorempixel.com/300/300/people/4',
'http://lorempixel.com/300/300/people/5'*/
];
// LOAD IMAGES
function loadImages(arrayOfImages, index) {
index = index || 0;
if (arrayOfImages && arrayOfImages.length && arrayOfImages.length > index) {
var img = new Image();
img.onload = function() {
var pane = layer2.get('#pane_' + index );
pane.fill('').fillPatternImage(img);
stage.draw(); // <<< THIS WORKS
var tween = new Kinetic.Tween({
node: pane,
duration: 1,
opacity: 0.8,
easing: Kinetic.Easings.BackEaseOut,
onFinish: function() {
loadImages(arrayOfImages, index + 1
}
}).play; // <<< NOT WORKING
//setTimeout(function(){ loadImages(arrayOfImages, index + 1); }, 200);
};
img.src = arrayOfImages[index];
}
}
function start() {
console.log("numOfPanes: " +urls.length);
for (i = 0; i <= urls.length; i++) {
var shape0 = new Kinetic.RegularPolygon({
x: i * 15,
y: i * 15,
sides: 5,
rotation: i * 10,
radius: 70,
fill: 'Red'
});
var shape1 = new Kinetic.RegularPolygon({
x: i * 15,
y: i * 15,
sides: 5,
rotation: i * 10,
radius: 70,
opacity: 0,
fillPatternOffset: {x:-220, y:70},
id: 'pane_' + i,
name: 'pane',
fill: 'Green'
});
layer.add(shape0);
layer2.add(shape1);
}
stage.add(layer,layer2);
}
// INIT
start();
// trigger loadimages() with console
loadImages(urls);
play is a function. So you should call it.
var tween = new Kinetic.Tween({
node: pane,
duration: 1,
opacity: 0.8,
easing: Kinetic.Easings.BackEaseOut,
onFinish: function() {
loadImages(arrayOfImages, index + 1
}
}).play();
Also: when you are finding node with get function it returns Collection. So if you need just node use:
var pane = layer2.get('#pane_' + index )[0];