2014-10-28 55 views
0

我使用ng-repeat指令生成了一些<rect> s,<animate>孩子。SVG ng-repeat'ed元素上的SMIL動畫僅在頁面加載時發生

在頁面加載時,動畫被正確觸發,所有事情都按預期發生。 但是,當我添加新的<rect>時,動畫不會發生。

以下代碼段演示了此行爲:

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="200"> 
 
    <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" /> 
 
     <animate attributeName="height" from="0" to="{{rect.height}}" dur="1s" repeatCount="1" /> 
 
    </rect> 
 
    </svg> 
 
    <button ng-click="newRect()">One more</button> 
 
</div>

在加載的例子中,4 <rect>會出現,從底部到頂部動畫。但是,當按下「One more」按鈕時,將添加新的<rect>而不顯示動畫(在Firefox 35和Chrome 38上測試的行爲)。

如何觸發新的<rect> s的動畫?

回答

3

動畫元素的默認開始時間(相當於begin="0s")總是相對於SVG加載時間進行測量。即使在頁面加載後動態創建動畫元素,情況也是如此。

如果您需要其他開始時間,您需要(a)爲begin屬性顯式設置不同的值,或者(b)使用動畫元素DOM對象的beginElement() or beginElementAt(offsetTime)方法。由於您使用腳本創建元素,並且希望在插入元素後立即啓動元素,因此beginElement()方法可能是最簡單的方法。

編輯補充:

如果beginElement不工作,因爲角的js不爲您提供直接訪問創建的元素,你可以爲begin屬性使用事件格式。如果begin屬性包含DOM事件的名稱(或分號分隔的時間和事件名稱列表),則該動畫將在該事件發生時開始。默認情況下,將在動畫將影響的元素上偵聽事件 - 您的情況下的矩形。 (您可以使用elementID.eventName格式將動畫綁定到不同的元素)。

一旦添加動畫就開始動畫的訣竅是將其鏈接到一個很少使用的DOM-mutation events,特別是DOMNodeInserted。這樣,當您將動畫節點添加爲<rect>的子節點時,或者將<rect>添加到SVG時,將立即觸發該事件,然後是動畫。

如果您想插入元素和觸發動畫之間的延遲,請使用eventName + timeOffset格式。

Here is the proof of concept with vanilla JS (as a fiddle)

這裏是你修改後的代碼片段; JS代碼是一樣的,只是角模板發生了變化:

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>

我不知道你是否有意想有酒吧開始從底線10px的偏移。如果這是偶然的,您可以通過將第一個動畫的from值設置爲rect.height + rect.y來修復它。

我已經測試了最新的Chrome和Firefox中的小提琴。如果您需要支持較舊的瀏覽器,尤其是如果您使用SMIL polyfill支持較舊的IE,則需要進行測試以確保DOM突變事件正確引發。

+0

我嘗試了'beginElement()'方法。然而,因爲我在觸發它之前需要Javascript訪問''元素,所以我需要在'setTimeout(fn,0)'中包裝這個調用。在某些情況下,這會產生一個閃爍,用戶可以在動畫觸發前看到完整的''(現在我想寫一個段落並且無意中提交了答覆,經驗教訓!)。不過,我會試着看看我能不能用(a)方法做出一些事情,謝謝。 – Daniel 2014-10-29 10:15:19

+0

@Daniel如果你還沒有弄清楚自己,請參閱編輯。 – AmeliaBR 2014-10-29 15:41:44

+0

我的確嘗試過這個解決方案,但不知不覺地寫了'this.DOMNodeInserted'而不是'DOMNodeInserted'。感謝您的編輯,這非常有幫助。至於其他的一點:10px的偏移確實是無意的,我在我的代碼中糾正了它,但認爲它對於手頭的問題不夠重要。關於第二點:不,在此關頭,較老的IE並不是問題,幸運的是。 – Daniel 2014-10-29 18:10:24