The default begin time for an animation element (equivalent to begin="0s"
) is always measured relative to the SVG load time. This is true even if you create the animation element dynamically after page load.
If you want any other begin time, you will need to either (a) explicitly set a different value for the begin
attribute, or (b) use the beginElement()
or beginElementAt(offsetTime)
methods of the animation element DOM objects. Since you're creating the elements with scripts and you want them to start right after inserting them, the beginElement()
method is probably the easiest approach.
Edited to add:
If beginElement
isn't working because angular-js doesn't provide you with direct access to the created element, you can make use of the event format for the begin
attribute. If the begin attribute contains the name of a DOM event (or a semi-colon separated list of times and event names), the animation will begin when that event occurs. By default, the event will be listened for on the element that the animation will affect—the rectangle in your case. (You can tie the animation to a different element using the elementID.eventName
format).
The trick to get the animation to start as soon as it is added is to link it to one of the rarely used DOM-mutation events, specifically DOMNodeInserted
. That way, when you add the animation node as a child of the <rect>
, or when you add the <rect>
to the SVG, the event and then the animation will be triggered immediately.
If you want a delay between inserting the element and triggering the animation, use the eventName + timeOffset
format.
Here is the proof of concept with vanilla JS (as a fiddle).
And here is your modified snippet; the JS code is the same, only the angular template has changed:
function Controller($scope) {
$scope.rects = [];
var spacing = 5;
var width = 10;
var height = 100;
var x = 10;
var y = 10;
$scope.newRect = function() {
$scope.rects.push({
x: x,
y: y,
width: width,
height: height
});
x += spacing + width;
};
for (var i = 0; i < 5; i++) {
$scope.newRect();
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="Controller">
<svg width="1000" height="120">
<rect ng-repeat="rect in rects" x="{{rect.x}}" y="{{rect.y}}"
height="{{rect.height}}" width="{{rect.width}}">
<animate attributeName="y" from="{{rect.height}}" to="{{rect.y}}"
dur="1s" repeatCount="1" begin="DOMNodeInserted"/>
<animate attributeName="height" from="0" to="{{rect.height}}"
dur="1s" repeatCount="1" begin="DOMNodeInserted"/>
</rect>
</svg>
<button ng-click="newRect()">One more</button>
</div>
I'm not sure whether you intentionally want to have the bars start 10px offset from the bottom line. If that was accidental you can fix it by setting the from
value of the first animation to rect.height + rect.y
.
I've tested the fiddle in the latest Chrome and Firefox. If you need to support older browsers, especially if you're supporting older IE with a SMIL polyfill, you'll want to test to make sure the DOM mutation events are being thrown correctly.
beginElement()
approach. However, since I need Javascript access to the<animate>
element before triggering it, I needed to wrap the call in asetTimeout(fn, 0)
. In some cases this creates a flicker where the user can see the full<rect>
before the animation is triggered (and now I wanted to write a paragraph and unwittingly submitted the reply. Lesson learnt!). I'll try to see if I can make something with the (a) approach, though, thanks. – Qualification