I am trying to get Squib to embed images. I have an excel doc (totm.xlsx) that has fields for Title, Gold, Description, etc. In the Excel doc, most of the cards have :A: or :M: in the description and I would like to replace those with a small SVG icon.
The following code coughs up 'unidentified local method [embed]' and a litany of other errors:
require 'squib'
Squib::Deck.new(cards: 54) do
background color: :white
data = xlsx file: 'totm.xlsx'
text str: data['Title'], x: 250, y: 55, font: 'Arial 12'
text str: data['Gold'], x: 65, y: 65, font: 'Arial 12'
text(str: data['Description'], x: 65, y: 600, font: 'Arial 12') do [embed]
embed.svg key: ':A:', width: 28, height: 28, file: 'battle-axe.svg'
embed.svg key: ':M:', width: 28, height: 28, file: 'burning-meteor.svg'
end
text str: data['Flavortext'], x: 65, y: 100, font: 'Arial 12'
text str: data['Type'], x: 65, y: 400, font: 'Arial 12'
save_sheet prefix: 'totm_sheet_', margin: 75, gap: 5, trim: 37
end
In the examples with embedding text, the code always uses a single named string, embed_text, but I would like to call an array of strings.
embed_text = 'Take 1 :tool: and gain 2 :health:.'
text(str: embed_text, font: 'Sans', font_size: [18, 32, 45],
x: 0, y: 0, width: 180, height: 300, valign: :bottom,
align: :left, ellipsize: false, justify: false, hint: :cyan) do |embed|
embed.svg key: ':tool:', width: 28, height: 28, file: 'spanner.svg'
embed.svg key: ':health:', width: 28, height: 28, file: 'glass-heart.svg'
end
You have brackets around your [embed]; as the code example shows, it should be using vertical bars, like |embed|.
The Problem:
I'm using Snap SVG to draw and subsequently animate 4 graphics (desktop, laptop, tablet, phone), so that they morph into one another every 5 seconds. The devices are built using basic lines and shapes, as well as a PNG screenshot for each device. You can see it in action here. My original code is as follows:
var makeDesktop = function() {
deviceOuter.animate({width: 420, height: 300, rx: 20, ry: 20, transform: 'T0,0'}, 1000, mina.easeinout);
screenOuter.animate({width: 380, height: 220, transform: 'T0,0'}, 1000, mina.easeinout);
screenImageDesktop.animate({width: 380, height: 220, transform: 'T0,0', opacity: 1}, 1000, mina.easeinout);
screenImageLaptop.animate({width: 380, height: 220, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout);
screenImageTablet.animate({width: 380, height: 220, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout);
screenImagePhone.animate({width: 380, height: 220, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout);
camera.animate({r: 2.5, transform: 'T0,0'}, 1000, mina.easeinout);
desktopDivider.animate({d: "M0,260, 420,260", opacity: 1}, 1000, mina.easeinout);
laptopMidDivider.animate({d: "M20,300, 400,300", opacity: 0}, 1000, mina.easeinout);
laptopMidLeft.animate({d: "M20,300, 20,300", opacity: 0}, 1000, mina.easeinout);
laptopMidRight.animate({d: "M400,300, 400,300", opacity: 0}, 1000, mina.easeinout);
deviceBaseLeft.animate({d: "M165,300 Q160,340 140,340", opacity: 1}, 1000, mina.easeinout);
deviceBaseRight.animate({d: "M255,300 Q260,340 280,340", opacity: 1}, 1000, mina.easeinout);
deviceBaseBottom.animate({d: "M140,340, 280,340", opacity: 1}, 1000, mina.easeinout);
mobileButton.animate({r: 10, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout);
$("#btn-desktop").addClass("active");
devicePosition = 0;
};
I've also tried reformatting to make use of a set:
var makeDesktop = function() {
var set = Snap([deviceOuter, screenOuter, screenImageDesktop, screenImageLaptop, screenImageTablet, screenImagePhone, camera, desktopDivider, laptopMidDivider, laptopMidLeft, laptopMidRight, deviceBaseLeft, deviceBaseRight, deviceBaseBottom, mobileButton]);
set.animate([{width: 420, height: 300, rx: 20, ry: 20, transform: 'T0,0'}, 1000, mina.easeinout], [{width: 380, height: 220, transform: 'T0,0'}, 1000, mina.easeinout], [{width: 380, height: 220, transform: 'T0,0', opacity: 1}, 1000, mina.easeinout], [{width: 380, height: 220, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout], [{width: 380, height: 220, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout], [{width: 380, height: 220, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout], [{r: 2.5, transform: 'T0,0'}, 1000, mina.easeinout], [{d: "M0,260, 420,260", opacity: 1}, 1000, mina.easeinout], [{d: "M20,300, 400,300", opacity: 0}, 1000, mina.easeinout], [{d: "M20,300, 20,300", opacity: 0}, 1000, mina.easeinout], [{d: "M400,300, 400,300", opacity: 0}, 1000, mina.easeinout], [{d: "M165,300 Q160,340 140,340", opacity: 1}, 1000, mina.easeinout], [{d: "M255,300 Q260,340 280,340", opacity: 1}, 1000, mina.easeinout], [{d: "M140,340, 280,340", opacity: 1}, 1000, mina.easeinout], [{r: 10, transform: 'T0,0', opacity: 0}, 1000, mina.easeinout])
$("#btn-desktop").addClass("active");
devicePosition = 0;
};
My Question
Seeing as every animation's duration is 1000ms and has the same easing curve, is there a less expensive way of achieving the same effect? Is there any performance benefit from using a set in this instance, as it drastically reduces readability.
Its hard to guess without seeing a jsfiddle to play around with different options and separate it from the rest of the webpage. I'd first play around with it separately.
The advantage 'should' be, that you are only running one timer, instead of a timer for every element, which certainly can create an overhead.
With a lot of elements, this would have an effect, I think in your case, its borderline. It may perform a little better, but I'm not sure it would be noticeable. Only you can tell that. One thing I would say, is test them both on mobile, as that is often a lowest performing case for svg animation.
Other things that may be worth looking into...
Are there multiple transforms applied to an element (eg a transformed element inside a group or another container thats transformed), as that will slow things down.
CSS transforms/animations can often perform better, but you will want to check support for browsers/devices. Velocity.js could be worth looking at.
Does having a viewBox affect performance, could it be quicker if everything was initially the right size, what about the images ?
Regarding readability, everything doesn't have to be kept onto 2 lines in the alternative version, you can still make things readable I would think (if not more readable). Eg could it be rewritten like..
var deviceOuterChanges = [{width: 420, height: 300, rx: 20, ry: 20, transform: 'T0,0'}, 1000, mina.easeinout];
var screenOuterChanges = [{width: 380, height: 220, transform: 'T0,0'}, 1000, mina.easeinout];
....
var set = Snap([deviceOuter, screenOuter.... mobileButton])
.animate([ deviceOuterChanges,
screenOuterChanges,
...
]);
You could also reuse some of them, as all of the screenImage objects have the same animation attributes.
In my application I have several shapes that intersect with each other, for example, Shape A, Shape B and Shape C. A need to draw a polyline from Shape A to Shape C through Shape B, so the line lies inside all the shapes and crosses them almost at center.
What is the most efficient way to implement this? Here is the code of example: http://jsfiddle.net/dselkirk/ZMUkE/.
I need to draw a yellow line, but not manually:
var stage = new Kinetic.Stage({
container: 'container',
width: 850,
height: 750
});
var layer = new Kinetic.Layer({
x: 0,
y: 0
});
var rect = new Kinetic.Rect({
x: 100,
y: 100,
width: 80,
height: 60,
fill: '#E67E22'
});
var rect2 = new Kinetic.Rect({
x: 640,
y: 70,
width: 40,
height: 70,
fill: '#3498DB',
});
var circle = new Kinetic.Circle({
x: 600,
y: 150,
radius: 70,
fill: '#2ECC71',
});
var polyOne = new Kinetic.Polygon({
x: 80,
y: 100,
points: [73, 192, 73, 160, 340, 23, 500, 109, 499, 139, 342, 93],
fill: '#9B59B6'
});
var polyTwo = new Kinetic.Polygon({
x: 100,
y: 160,
points: [-5, 0, 75, 0, 70, 10, 70, 60, 60, 90, 61, 92, 64, 96, 66, 100, 67, 105, 67, 110, 67, 113, 66, 117, 64, 120, 63, 122, 61, 124, 58, 127, 55, 129, 53, 130, 50, 130, 20, 130, 17, 130, 15, 129, 12, 127, 9, 124, 7, 122, 6, 120, 4, 117, 3, 113, 3, 110, 3, 105, 4, 100, 6, 96, 9, 92, 10, 90, 0, 60, 0, 10],
fill: '#e74c3c'
});
var imageObj = new Image();
imageObj.onload = function () {
var yoda = new Kinetic.Image({
x: 120,
y: 120,
image: imageObj,
width: 15,
height: 18
});
// add the shape to the layer
layer.add(yoda);
stage.add(layer);
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg';
var imageObj2 = new Image();
imageObj2.onload = function () {
var dart = new Kinetic.Image({
x: 650,
y: 80,
image: imageObj2,
width: 20,
height: 21
});
layer.add(dart);
stage.add(layer);
};
var yellowLine = new Kinetic.Line({
points: [{x:125,y:140},{x:125,y:280}, {x:425,y:150}, {x:555,y:220}, {x:655,y:100}],
stroke: 'yellow',
strokeWidth: 2,
lineJoin: 'round',
dashArray: [33, 10]
});
layer.add(polyOne);
layer.add(polyTwo);
layer.add(yellowLine);
layer.add(circle);
layer.add(rect);
layer.add(rect2);
stage.add(layer);
yellowLine.moveToTop();
For now I think the algorithm should be:
1) Find intersection points of all shapes.
2) Draw lines between these intersectiopn points. If any line point lies outside the shape - move it horizontally/vertically till it lies inside the shape.
But this algorithm seems not efficient at all.