SVG animation triggering on load rather than on DOM insertion - javascript

The code below animates a SVG circle changing color and works as expected.
If the call to SVG.addAnimatedCircle(this.root) is made from within the callback method (instead of where it is below, inside the constructor), the animation starts when the document is loaded — and is therefore invisible unless the window is clicked — rather than when the event is triggered.
class SVG {
constructor() {
const root = document.createElementNS(
'http://www.w3.org/2000/svg', 'svg');
root.setAttribute('viewBox', '-50 -50 100 100');
this.root = root;
this.callback = this.callback.bind(this);
window.addEventListener('click', this.callback);
SVG.addAnimatedCircle(this.root);
}
callback() {
// SVG.addAnimatedCircle(this.root);
}
static addAnimatedCircle(toElement) {
const el = document.createElementNS(
'http://www.w3.org/2000/svg', 'circle');
el.setAttribute('cx', '0');
el.setAttribute('cy', '0');
el.setAttribute('r', '10');
el.setAttribute('fill', 'red');
toElement.appendChild(el);
const anim = document.createElementNS(
'http://www.w3.org/2000/svg', 'animate');
anim.setAttribute('attributeName', 'fill');
anim.setAttribute('from', 'blue');
anim.setAttribute('to', 'red');
anim.setAttribute('dur', '3s');
el.appendChild(anim);
}
}
const svg = new SVG();
document.body.appendChild(svg.root);
(The above doesn't need to be inside a class of course, I'm simplifying a more complex class).
Why is that? Isn't the animation supposed to start when the element is created and added to the DOM?

The <animate> element you create will have its begin attribute computed to 0s (since unset).
This 0s value is relative to the "document begin time", which itself in this HTML document corresponds to the root <svg>'s current time.
This means that if you do create such an <animate> element after its root <svg> element has been in the DOM, its animation state will depend on how long the root <svg> element has been in the DOM:
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
// will fully animate
circles[0].append(makeAnimate());
// will produce only half of the animation
setTimeout(() => {
circles[1].append(makeAnimate());
}, duration * 500);
// will not animate
setTimeout(() => {
circles[2].append(makeAnimate());
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
<p>left circle starts immediately, and fully animates</p>
<p>middle circle starts after <code>duration / 2</code> and matches the same position as left circle</p>
<p>right circle starts after <code>duration</code>, the animation is already completed by then, nothing "animates"</p>
We can set the <svg>'s current time trough its SVGSVGElement.setCurrentTime() method.
So to create an <animate> that would start at the time it got created, no matter when it is, we could use this, however, this will also affect all the other <animate> that are already in the <svg>:
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
circles[0].append(makeAnimate());
root.setCurrentTime(0); // reset <animate> time
setTimeout(() => {
circles[1].append(makeAnimate());
root.setCurrentTime(0); // reset <animate> time
}, duration * 500);
setTimeout(() => {
circles[2].append(makeAnimate());
root.setCurrentTime(0); // reset <animate> time
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
So, while it may do for some users, in most cases it's probably better to instead set only the <animate>'s begin attribute.
Luckily, we can also get the current time, with the SVGSVGElement.getCurrentTime() method.
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
circles[0].append(makeAnimate());
setTimeout(() => {
circles[1].append(makeAnimate());
}, duration * 500);
setTimeout(() => {
circles[2].append(makeAnimate());
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
// set the `begin` to "now"
anim.setAttribute("begin", root.getCurrentTime() + "s");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>
But the way we usually do this is to use the API fully and control it all through JS, since you already did start using JS.
To do so, we set the begin attribute to "indefinite", so that it doesn't start automatically, and then we call the SVGAnimateElement (<animate>)'s beginElement() method, which will start the animation manually, when we want:
const root = document.querySelector("svg");
const circles = document.querySelectorAll("circle");
const duration = 3;
{
const animate = makeAnimate();
circles[0].appendChild(animate);
animate.beginElement();
}
setTimeout(() => {
const animate = makeAnimate();
circles[1].appendChild(animate);
animate.beginElement();
}, duration * 500);
setTimeout(() => {
const animate = makeAnimate();
circles[2].appendChild(animate);
animate.beginElement();
}, duration * 1000);
function makeAnimate() {
const anim = document.createElementNS("http://www.w3.org/2000/svg", "animate");
anim.setAttribute("attributeName", "fill");
anim.setAttribute("from", "blue");
anim.setAttribute("to", "red");
anim.setAttribute("fill", "freeze");
anim.setAttribute("dur", duration + "s");
// set the `begin` to "manual"
anim.setAttribute("begin", "indefinite");
return anim;
}
circle { fill: blue }
<svg height="60">
<circle cx="30" cy="30" r="25"/>
<circle cx="90" cy="30" r="25"/>
<circle cx="150" cy="30" r="25"/>
</svg>

(Not the answer, just presentation of the problem for possible accomodation into the question and/or proper answer suggested by OP in their comment.
<style>svg {outline: #0FF6 solid; outline-offset: -2px;}</style>
<table role="presentation" border><tr><td>
1. Static SVG with animated circle:
<td>
<svg viewBox="-50 -50 100 100" width="30" height="30">
<circle r="40" fill="black">
<animate begin="0.5s" fill="freeze" attributeName="fill" from="blue" to="red" dur="5s"></animate>
</circle>
</svg>
<tr><td>
2. Empty SVG,
<button onclick='
emptySVG.innerHTML = document.querySelector("circle").outerHTML;
'>Put similar animated circle into it</button>:
<td>
<svg id="emptySVG" viewBox="-50 -50 100 100" width="30" height="30">
<!-- empty -->
</svg>
<tr><td>
3. <button onclick='
sampleCell.innerHTML = document.querySelector("svg").outerHTML
'>Create new SVG with animated circle</button>:
<td id="sampleCell">
(here.)
</table>
<p>
<button onclick="location.reload()">Reload page</button>
<button onclick="
[...document.querySelectorAll('animate')].forEach(a=>{
//a.setAttribute('begin','indefinite'); // does not seem to be necessary
a.beginElement();
})
">Reset all animations</button>
Putting animated circle to second SVG produces state that corresponds with already elapsed duration in existing empty SVG: it matches the first one, so it either runs in sync or is finished. Goal is to run the animation upon circle's appearance.

Related

3 sec animation that repeats with requestAnimationFrame

I have an SVG with an animation using requestAnimationFrame and the animation works but what I want it to do is animate over 3 secs and then repeat, so the animation for the circle will animate the dasharray and dashoffsset over 3seconds I don't know how to do this using css because it requires a calculation in javascript using Math.Pi can someone see if they can make this animation work the way I described. codepen.io
HTML
<svg width="20" height="20" viewBox="0 0 20 20">
<circle cx="10" cy="10" r="7" fill="none" stroke="#e6e6e6" stroke-width="6" />
<circle id="shape" cx="10" cy="10" r="7" fill="none" stroke="#ff0000" stroke-width="6" stroke-linecap="square" />
</svg>
CSS
#shape {
fill: none;
stroke: red;
stroke-width: 6;
transition: all 4s ease-in-out;
transform: rotate(-90deg);
transform-origin: 50%;
}
JavaScript
var endPercentage = 100;
var shape = document.getElementById('shape');
var circumference = Math.PI * parseFloat(shape.getAttribute('r')) * 2;
shape.setAttribute('stroke-dasharray', circumference);
shape.setAttribute('stroke-dashoffset', circumference);
var updatePercentage = function () {
var dashOffset = parseFloat(getComputedStyle(shape)['stroke-dashoffset']);
var curPercentage = Math.floor((100 * (circumference - dashOffset)) / circumference) || 0;
document.getElementById('perc').getElementsByClassName('value')[0].innerText = curPercentage;
if (curPercentage < endPercentage) {
window.requestAnimationFrame(updatePercentage);
}
}
var animateShape = function (percent) {
var offset = circumference - (circumference * percent / 100);
shape.setAttribute('stroke-dashoffset', offset);
window.requestAnimationFrame(updatePercentage);
}
setTimeout(function () {
animateShape(endPercentage);
}, 0);
You can define pi with:
:root {
--PI: 3.14159265358979;
}
...
stroke-dashoffset: calc(2 * var(--PI) * 7);
/* 7 is the radius of your circle */
With a combination of this and calc, you should be able to get a pure CSS solution.
If you still want to go via the JS route, I recommend:
shape.animate([
{ strokeDashoffset: 0 },
{ strokeDashoffset: circumference }
], { duration: 3000, iterations: Infinity });
Warning: polyfill required for older browsers.

Elements visible only from Inspector, SVG

I've tried to create more circles with button, but it doesnt work. Theyre shown in the Mozilla Inspector after click:
inspector
but theyre not visible for me. I've seen similiar problems here, but there wasn't single one that works. Can you help me please? Atm I have no idea what to do.
circle.js
class Circle {
constructor(id, posx, posy, r, fill) {
this.id = id;
this.posx = posx;
this.posy = posy;
this.r = r;
this.fill = fill;
}}
creator.js
function createCircle() {
let color = ["blue", "black", "red", "green", "purple", "orange", "yellow"]
const circle = new Circle("node", 100, 100, 50, color[0]);
var node = document.createElement("CIRCLE");
node.setAttribute("id", "node");
node.setAttribute("cx", circle.posx);
node.setAttribute("cy", circle.posy);
node.setAttribute("r", circle.r);
node.setAttribute("fill", circle.fill);
document.getElementById("frame").appendChild(node);
console.log(circle.fill);}
body and from index.html
<body onload="myFunction()">
<svg id="sss" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg id="frame" width="1020px" height="820px" viewBox="0 0 1020 820">
<circle id="circle0" cx="100" cy="100" r="50" fill="black" />
</svg>
</svg>
<button onclick="createCircle()">Create circle</button></body>
SVG elements are from a different namespace than typical HTML elements. An HTML document can mix tags from different XML dialects, for example XHTML, which are the standard HTML elements, but also different dialects as well, like the SVG namespace. In order to create the right element from the correct namespace you need to use a different JavaScript method that lets you specify the namespace:
document.createElementNS(namespace, element);
The first argument is the namespace so you should use: "http://www.w3.org/2000/svg", and the second is the element, in this case "circle". So try:
var node = document.createElementNS("http://www.w3.org/2000/svg", "circle");
If you're more interested, check out MDN docs:
https://developer.mozilla.org/en-US/docs/Web/SVG/Namespaces_Crash_Course
https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
#gdanielyan's answer is a good answer. Here is a demo:
class Circle {
constructor(id, posx, posy, r, fill) {
this.id = id;
this.posx = posx;
this.posy = posy;
this.r = r;
this.fill = fill;
}}
function createCircle() {
let color = ["blue", "red", "green", "purple", "orange", "yellow"]
const circle = new Circle("node", 100, 100, 50, color[0]);
var node = document.createElementNS("http://www.w3.org/2000/svg","circle");
node.setAttributeNS(null,"id", "node");
node.setAttributeNS(null,"cx", circle.posx + Math.random()*100);
node.setAttributeNS(null,"cy", circle.posy + Math.random()*100);
node.setAttributeNS(null,"r", circle.r);
let _color=color[~~(Math.random()*(color.length))];
//console.log(_color)
node.setAttributeNS(null,"fill", _color);
document.getElementById("frame").appendChild(node);
}
<svg id="sss" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg id="frame" width="1020px" height="820px" viewBox="0 0 1020 820">
<circle id="circle0" cx="100" cy="100" r="50" fill="black" />
</svg>
</svg>
<button onclick="createCircle()">Create circle</button>

Call JavaScript function from SVG file

I have a SVG file and a JavaScript file.
In my SVG file I have some code, but it is this small amount of code I'm wondering about:
<g id="zoomButton">
<g onclick="zoom()" transform="scale(x,y)">
<rect x="640" y="20" width="140" height="40"
style="fill:white;stroke:red;stroke-width:2" />
<text x="710" y="49"
style="fill:red;font-size:25px;text-anchor:middle">Zoom</text>
</g>
</g>
You see that I want the function zoom() to be called when this button is clicked.
Now, in my JavaScript file, I have this function called zoom():
function zoom() {
alert("heyho");
}
However, this function is not triggered when I press the button created in the SVG file. I only get the error message:
Uncaught ReferenceError: zoom is not defined
I just want to be able to make that JavaScript function to be called when I press this button.
Furthermore, since this is a zoom-function after all and I want to do some scaling, how do I access the x and y inside transform="scale(x,y) in the zoomButton in the SVG file from the JavaScript file?
Edit:
The full code in the SVG file is as follows (game.svg):
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="600px"
xmlns="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:a="http://www.adobe.com/svg10-extensions" a:timeline="independent"
onload="top.load(evt)">
<defs>
<clipPath id="gameareaclip">
<rect x="20" y="20" width="600" height="560"/>
</clipPath>
<pattern id="background_pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect width="20" height="20" style="fill:purple"/>
<circle cx="10" cy="10" r="8" style="fill:black"/>
</pattern>
<radialGradient id="player_color">
<stop offset="0.0" style="stop-color:yellow;stop-opacity:1"/>
<stop offset="0.8" style="stop-color:yellow;stop-opacity:1"/>
<stop offset="1.0" style="stop-color:orange;stop-opacity:1"/>
</radialGradient>
</defs>
<rect width="100%" height="100%" style="fill:url(#background_pattern);stroke:orange;stroke-width:4" />
<rect x="20" y="20" width="600" height="560" style="fill:black;stroke:red;stroke-width:5" />
<!-- Add your button here -->
<g id="zoomKnapp">
<g onclick="zoom()" transform="scale(x,y)">
<rect x="640" y="20" width="140" height="40"
style="fill:white;stroke:red;stroke-width:2" />
<text x="710" y="49"
style="fill:red;font-size:25px;text-anchor:middle">Zoom</text>
</g>
</g>
<g style="clip-path:url(#gameareaclip)">
<g transform="translate(20,20)">
<g id="gamearea" transform="translate(0,0)" width="600" height="560">
<rect x="0" y="0" width="600" height="560" style="fill:lightgrey" />
<g id="platforms">
<!-- Add your platforms here -->
<rect id="svg_37" height="20" width="300" y="100" x="0" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#000000" fill="#5fbf00"/>
<rect id="svg_38" height="20" width="300" y="170" x="300" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#000000" fill="#5fbf00"/>
<rect id="svg_39" height="20" width="300" y="240" x="0" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#000000" fill="#5fbf00"/>
<rect id="svg_40" height="20" width="300" y="310" x="300" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#000000" fill="#5fbf00"/>
<rect id="svg_41" height="20" width="300" y="380" x="0" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#000000" fill="#5fbf00"/>
<rect id="svg_42" height="20" width="300" y="450" x="300" stroke-linecap="null" stroke-linejoin="null" stroke-width="0" stroke="#000000" fill="#5fbf00"/>
</g>
<g id="player">
<circle cx="20" cy="20" r="20" style="fill:url(#player_color);stroke:black;stroke-width:2"/>
<ellipse cx="15" cy="15" rx="3" ry="6" style="fill:black"/>
<ellipse cx="25" cy="15" rx="3" ry="6" style="fill:black"/>
<path d="M10,25 l20,0 q0,8 -10,8 t-10,-8" style="fill:orange;stroke:black;stroke-black:2"/>
</g>
</g>
</g>
</g>
</svg>
The HTML file (game.html):
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>SVG Game</title>
<script language="JavaScript" src="game.js"></script>
</head>
<body style="text-align: center">
<embed src="game.svg" type="image/svg+xml" width="800" height="600" />
</body>
</html>
The JavaScript (game.js) code:
// The point and size class used in this program
function Point(x, y) {
this.x = (x)? parseFloat(x) : 0.0;
this.y = (y)? parseFloat(y) : 0.0;
}
function Size(w, h) {
this.w = (w)? parseFloat(w) : 0.0;
this.h = (h)? parseFloat(h) : 0.0;
}
// Helper function for checking intersection between two rectangles
function intersect(pos1, size1, pos2, size2) {
return (pos1.x < pos2.x + size2.w && pos1.x + size1.w > pos2.x &&
pos1.y < pos2.y + size2.h && pos1.y + size1.h > pos2.y);
}
function zoom() {
alert("heiho");
}
// The player class used in this program
function Player() {
this.node = svgdoc.getElementById("player");
this.position = PLAYER_INIT_POS;
this.motion = motionType.NONE;
this.verticalSpeed = 0;
}
Player.prototype.isOnPlatform = function() {
var platforms = svgdoc.getElementById("platforms");
for (var i = 0; i < platforms.childNodes.length; i++) {
var node = platforms.childNodes.item(i);
if (node.nodeName != "rect") continue;
var x = parseFloat(node.getAttribute("x"));
var y = parseFloat(node.getAttribute("y"));
var w = parseFloat(node.getAttribute("width"));
var h = parseFloat(node.getAttribute("height"));
if (((this.position.x + PLAYER_SIZE.w > x && this.position.x < x + w) ||
((this.position.x + PLAYER_SIZE.w) == x && this.motion == motionType.RIGHT) ||
(this.position.x == (x + w) && this.motion == motionType.LEFT)) &&
this.position.y + PLAYER_SIZE.h == y) return true;
}
if (this.position.y + PLAYER_SIZE.h == SCREEN_SIZE.h) return true;
return false;
}
Player.prototype.collidePlatform = function(position) {
var platforms = svgdoc.getElementById("platforms");
for (var i = 0; i < platforms.childNodes.length; i++) {
var node = platforms.childNodes.item(i);
if (node.nodeName != "rect") continue;
var x = parseFloat(node.getAttribute("x"));
var y = parseFloat(node.getAttribute("y"));
var w = parseFloat(node.getAttribute("width"));
var h = parseFloat(node.getAttribute("height"));
var pos = new Point(x, y);
var size = new Size(w, h);
if (intersect(position, PLAYER_SIZE, pos, size)) {
position.x = this.position.x;
if (intersect(position, PLAYER_SIZE, pos, size)) {
if (this.position.y >= y + h)
position.y = y + h;
else
position.y = y - PLAYER_SIZE.h;
this.verticalSpeed = 0;
}
}
}
}
Player.prototype.collideScreen = function(position) {
if (position.x < 0) position.x = 0;
if (position.x + PLAYER_SIZE.w > SCREEN_SIZE.w) position.x = SCREEN_SIZE.w - PLAYER_SIZE.w;
if (position.y < 0) {
position.y = 0;
this.verticalSpeed = 0;
}
if (position.y + PLAYER_SIZE.h > SCREEN_SIZE.h) {
position.y = SCREEN_SIZE.h - PLAYER_SIZE.h;
this.verticalSpeed = 0;
}
}
//
// Below are constants used in the game
//
var PLAYER_SIZE = new Size(40, 40); // The size of the player
var SCREEN_SIZE = new Size(600, 560); // The size of the game screen
var PLAYER_INIT_POS = new Point(0, 0); // The initial position of the player
var MOVE_DISPLACEMENT = 5; // The speed of the player in motion
var JUMP_SPEED = 15; // The speed of the player jumping
var VERTICAL_DISPLACEMENT = 1; // The displacement of vertical speed
var GAME_INTERVAL = 25; // The time interval of running the game
//
// Variables in the game
//
var motionType = {NONE:0, LEFT:1, RIGHT:2}; // Motion enum
var svgdoc = null; // SVG root document node
var player = null; // The player object
var gameInterval = null; // The interval
var zoom = 1.0; // The zoom level of the screen
//
// The load function for the SVG document
//
function load(evt) {
// Set the root node to the global variable
svgdoc = evt.target.ownerDocument;
// Attach keyboard events
svgdoc.documentElement.addEventListener("keydown", keydown, false);
svgdoc.documentElement.addEventListener("keyup", keyup, false);
// Remove text nodes in the 'platforms' group
cleanUpGroup("platforms", true);
// Create the player
player = new Player();
// Start the game interval
gameInterval = setInterval("gamePlay()", GAME_INTERVAL);
}
//
// This function removes all/certain nodes under a group
//
function cleanUpGroup(id, textOnly) {
var node, next;
var group = svgdoc.getElementById(id);
node = group.firstChild;
while (node != null) {
next = node.nextSibling;
if (!textOnly || node.nodeType == 3) // A text node
group.removeChild(node);
node = next;
}
}
//
// This is the keydown handling function for the SVG document
//
function keydown(evt) {
var keyCode = (evt.keyCode)? evt.keyCode : evt.getKeyCode();
switch (keyCode) {
case "N".charCodeAt(0):
player.motion = motionType.LEFT;
break;
case "M".charCodeAt(0):
player.motion = motionType.RIGHT;
break;
// Add your code here
case "Z".charCodeAt(0):
if (player.isOnPlatform()) {
//Jump
player.verticalSpeed = JUMP_SPEED;
}
break;
}
}
//
// This is the keyup handling function for the SVG document
//
function keyup(evt) {
// Get the key code
var keyCode = (evt.keyCode)? evt.keyCode : evt.getKeyCode();
switch (keyCode) {
case "N".charCodeAt(0):
if (player.motion == motionType.LEFT) player.motion = motionType.NONE;
break;
case "M".charCodeAt(0):
if (player.motion == motionType.RIGHT) player.motion = motionType.NONE;
break;
}
}
//
// This function updates the position and motion of the player in the system
//
function gamePlay() {
// Check whether the player is on a platform
var isOnPlatform = player.isOnPlatform();
// Update player position
var displacement = new Point();
// Move left or right
if (player.motion == motionType.LEFT)
displacement.x = -MOVE_DISPLACEMENT;
if (player.motion == motionType.RIGHT)
displacement.x = MOVE_DISPLACEMENT;
// Fall
if (!isOnPlatform && player.verticalSpeed <= 0) {
displacement.y = -player.verticalSpeed;
player.verticalSpeed -= VERTICAL_DISPLACEMENT;
}
// Jump
if (player.verticalSpeed > 0) {
displacement.y = -player.verticalSpeed;
player.verticalSpeed -= VERTICAL_DISPLACEMENT;
if (player.verticalSpeed <= 0)
player.verticalSpeed = 0;
}
// Get the new position of the player
var position = new Point();
position.x = player.position.x + displacement.x;
position.y = player.position.y + displacement.y;
// Check collision with platforms and screen
player.collidePlatform(position);
player.collideScreen(position);
// Set the location back to the player object (before update the screen)
player.position = position;
updateScreen();
}
//
// This function updates the position of the player's SVG object and
// set the appropriate translation of the game screen relative to the
// the position of the player
//
function updateScreen() {
// Transform the player
player.node.setAttribute("transform", "translate(" + player.position.x + "," + player.position.y + ")");
// Calculate the scaling and translation factors
// Add your code here
}
The script tag needs to go in the SVG file (game.svg) and not in the html file. Note that SVG script tags use xlink:href instead of src as the attribute that holds the script source.
<script xlink:href="game.js"></script>
You should also change onload="top.load(evt)" to just onload="load(evt)" in the root <svg> element.
And your third and main issue is that you have a function zoom() and a variable called zoom on line 114. Remove the variable as it isnt used and the zoom method will get called.

How can I animated multiple svg paths?

How can I animate multiple paths at the same time using an svg and javascript.
Here is the javascript I am using:
var path = document.querySelector('#Layer_1 path');
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition = 'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition = 'stroke-dashoffset 2s ease-in-out';
// Go!
path.style.strokeDashoffset = '0';
Link to jsfiddle
The problem is that you are only getting one path with document.querySelector()
So if we change that to document.querySelectorAll() and iterate over it for all paths it works.
Like this:
var paths = document.querySelectorAll('#Layer_1 path');
[].forEach.call(paths, function(path) {
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition =
'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition =
'stroke-dashoffset 2s ease-in-out';
// Go!
path.style.strokeDashoffset = '0';
})
<body>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="196.039px" height="185.064px" viewBox="0 0 196.039 185.064" enable-background="new 0 0 196.039 185.064" xml:space="preserve">
<g>
<g>
<path fill="none" stroke="#651D79" stroke-width="3.7953" stroke-miterlimit="10" d="M91.93,59.704
c3.347-5.793,3.347-15.27,0-21.063l-16.5-28.594c-3.347-5.79-10.823-7.791-16.616-4.448l-16.493,9.525
c-5.789,3.347-7.791,10.815-4.447,16.615l16.504,28.576c3.343,5.79,0.604,10.534-6.078,10.534H15.298
c-6.69,0-12.161,5.478-12.161,12.157v19.043c0,6.69,5.471,12.164,12.161,12.164h33.004c6.687,0,14.896-4.744,18.239-10.533
L91.93,59.704z" />
</g>
<g>
<g>
<path fill="none" stroke="#651D79" stroke-width="3.7953" stroke-miterlimit="10" d="M72.631,114.213
c-6.69,0-14.899,4.737-18.247,10.526l-16.508,28.594c-3.343,5.793-1.342,13.269,4.455,16.616l16.486,9.514
c5.796,3.347,13.269,1.345,16.615-4.448l16.5-28.583c3.347-5.8,8.817-5.8,12.165,0l16.497,28.576
c3.343,5.789,10.822,7.791,16.612,4.448l16.489-9.518c5.793-3.343,7.798-10.822,4.451-16.612l-16.5-28.576
c-3.347-5.8-11.556-10.537-18.239-10.537H72.631z" />
</g>
<g>
<path fill="none" stroke="#651D79" stroke-width="3.7953" stroke-miterlimit="10" d="M129.479,103.68
c3.347,5.789,11.556,10.526,18.246,10.526h33.012c6.69,0,12.164-5.467,12.164-12.157V83.006
c-0.007-6.69-5.478-12.164-12.168-12.164l-33.005,0.007c-6.686,0-9.421-4.744-6.078-10.534l16.497-28.576
c3.347-5.8,1.346-13.269-4.451-16.615l-16.489-9.525c-5.79-3.343-13.269-1.334-16.608,4.459l-16.5,28.576
c-3.347,5.789-3.347,15.27,0,21.07L129.479,103.68z" />
</g>
</g>
</g>
</svg>
</body>
You can loop over all paths
var paths = document.querySelectorAll('#Layer_1 path');
for (i = 0; i < paths.length; i++) {
var length = paths[i].getTotalLength();
// Clear any previous transition
paths[i].style.transition = paths[i].style.WebkitTransition ='none';
// Set up the starting positions
paths[i].style.strokeDasharray = length + ' ' + length;
paths[i].style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
paths[i].getBoundingClientRect();
// Define our transition
paths[i].style.transition = paths[i].style.WebkitTransition = 'stroke-dashoffset 3s ease-in-out';
// Go!
paths[i].style.strokeDashoffset = '0';
}
JSFiddle
Notice: You can try also Vivus library, is light and easy to maintain

Get width/height of SVG element

What is the proper way to get the dimensions of an svg element?
http://jsfiddle.net/langdonx/Xkv3X/
Chrome 28:
style x
client 300x100
offset 300x100
IE 10:
stylex
client300x100
offsetundefinedxundefined
FireFox 23:
"style" "x"
"client" "0x0"
"offset" "undefinedxundefined"
There are width and height properties on svg1, but .width.baseVal.value is only set if I set the width and height attributes on the element.
The fiddle looks like this:
HTML
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="1" fill="red" />
<circle cx="150" cy="50" r="40" stroke="black" stroke-width="1" fill="green" />
<circle cx="250" cy="50" r="40" stroke="black" stroke-width="1" fill="blue" />
</svg>
JS
var svg1 = document.getElementById('svg1');
console.log(svg1);
console.log('style', svg1.style.width + 'x' + svg1.style.height);
console.log('client', svg1.clientWidth + 'x' + svg1.clientHeight);
console.log('offset', svg1.offsetWidth + 'x' + svg1.offsetHeight);
CSS
#svg1 {
width: 300px;
height: 100px;
}
Use the getBBox function:
The SVGGraphicsElement.getBBox() method allows us to determine the coordinates of the smallest rectangle in which the object fits. [...]
http://jsfiddle.net/Xkv3X/1/
var bBox = svg1.getBBox();
console.log('XxY', bBox.x + 'x' + bBox.y);
console.log('size', bBox.width + 'x' + bBox.height);
FireFox have problemes for getBBox(), i need to do this in vanillaJS.
I've a better Way and is the same result as real svg.getBBox() function !
With this good post : Get the real size of a SVG/G element
var el = document.getElementById("yourElement"); // or other selector like querySelector()
var rect = el.getBoundingClientRect(); // get the bounding rectangle
console.log( rect.width );
console.log( rect.height);
I'm using Firefox, and my working solution is very close to obysky. The only difference is that the method you call in an svg element will return multiple rects and you need to select the first one.
var chart = document.getElementsByClassName("chart")[0];
var width = chart.getClientRects()[0].width;
var height = chart.getClientRects()[0].height;
SVG has properties width and height. They return an object SVGAnimatedLength with two properties: animVal and baseVal. This interface is used for animation, where baseVal is the value before animation. From what I can see, this method returns consistent values in both Chrome and Firefox, so I think it can also be used to get calculated size of SVG.
This is the consistent cross-browser way I found:
var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'],
widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
var svgCalculateSize = function (el) {
var gCS = window.getComputedStyle(el), // using gCS because IE8- has no support for svg anyway
bounds = {
width: 0,
height: 0
};
heightComponents.forEach(function (css) {
bounds.height += parseFloat(gCS[css]);
});
widthComponents.forEach(function (css) {
bounds.width += parseFloat(gCS[css]);
});
return bounds;
};
From Firefox 33 onwards you can call getBoundingClientRect() and it will work normally, i.e. in the question above it will return 300 x 100.
Firefox 33 will be released on 14th October 2014 but the fix is already in Firefox nightlies if you want to try it out.
Use .getAttribute()!
var height = document.getElementById('rect').getAttribute("height");
var width = document.getElementById('rect').getAttribute("width");
var x = document.getElementById('rect').getAttribute("x");
alert("height: " + height + ", width: " + width + ", x: " + x);
<svg width="500" height="500">
<rect width="300" height="100" x="50" y="50" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" id="rect"/>
</svg>
A save method to determine the width and height unit of any element (no padding, no margin) is the following:
let div = document.querySelector("div");
let style = getComputedStyle(div);
let width = parseFloat(style.width.replace("px", ""));
let height = parseFloat(style.height.replace("px", ""));

Categories

Resources