Hello,
I am trying to build an animation where GSAP will tween an SVG's d value to values that are stored in Angular scope variables. Currently I am using three "buffer" attributes to hold d values waiting to be used. These buffers are in the root controller scope, and are updated / refreshed by a function called in the onComplete of each tween in the timeline.
The svg is inside of a directive, and the bound data can be outputted to the dom inside the directive reflecting the changes made to the scope var. The problem is that the target value of the tween doesn't change, as if GSAP doesn't notice that the scope var has changed.
How can I get GSAP to notice that the bound var has changed, and tween to the new target value?
I tried to get this going in a code pen, but for some reason the ng-if that the directive waits for was never triggered, so there was a whole bunch of nothing on the screen. Sorry about that, attaching code here :
points.js is a temporary, external .js data source. It looks like this :
var pointsServicePoints = [
0,51.75885,
0.537109375,55.035365,
1.07421875,58.67774,
1.611335,69.03644,
2.1484375,73.44304,
2.68555375,91.91347,
3.22265625,102.512685,
3.7597725,110.4608538,
4.296875,117.23217,
4.83399125,117.9035,
5.37109375,120.02518,
5.90821,121.87956,
6.4453125,117.093845,
6.98242875,117.22029,
7.51953125,113.7137265,
8.0566475,109.850114,
8.59375,107.4311315,
9.13086625,114.337245,
9.66796875,113.621948,
10.205085,106.555064,
10.7421875,112.5433925,
11.27930375,107.3785735,
11.81640625,107.055696,
12.3535225,103.23841,
12.890625,107.243752,
13.42774125,108.128185,
13.9649125,104.9441085,......................etc for about 8,000 lines.
App.js
var cacheTestApp = angular.module('cacheTestApp',[])
.controller('CacheTestAppController', ['$scope','CacheService', 'PointConverterService', function($scope, CacheService, PointConverterService){
var cacheCont = this;
$scope.dPoints1 = "";
$scope.dPoints2 = "";
$scope.dPoints3 = "";
//Main Holder For X,Y from Cache
$scope.rawDataArray = [];
// next offset to use for dPoints - offset by 2 to get to start of next x,y pair.
var nextDPoint = 2;
// Points to render for each svg
var pointsPerSvg = 4000;
angular.element(document).ready(function(){
// Get points from service.
$scope.rawDataArray = CacheService.extractDataPoints();
// Get points to convert for initial SVGs
var activeSvg = $scope.rawDataArray.slice(0,pointsPerSvg);
nextDPoint+=2;
var firstSvg = $scope.rawDataArray.slice(nextDPoint,nextDPoint + pointsPerSvg);
nextDPoint+=2;
var secondSvg = $scope.rawDataArray.slice(nextDPoint,nextDPoint + pointsPerSvg);
nextDPoint+=2;
// Convert x,y points to SVGs.
$scope.dPoints1 = PointConverterService.convertToSvg(activeSvg);
$scope.dPoints2 = PointConverterService.convertToSvg(firstSvg);
$scope.dPoints3 = PointConverterService.convertToSvg(secondSvg);
});
/**
* Swap points in buffers.
* @param p
*/
$scope.advancePoints = function(p){
var t0 = performance.now();
var nextSvg = $scope.rawDataArray.slice(nextDPoint,nextDPoint + pointsPerSvg);
switch(p){
case 1 :
$scope.dPoints1 = PointConverterService.convertToSvg(nextSvg);
break;
case 2 :
$scope.dPoints2 = PointConverterService.convertToSvg(nextSvg);
break;
case 3 :
$scope.dPoints3 = PointConverterService.convertToSvg(nextSvg);
break;
}
nextDPoint += 2;
$scope.$apply();
};
}]);
directives.js
cacheTestApp.directive('svgDirective', [function(){
var linker = function(scope, element, attrs){
var lead1p1 = document.getElementById('lead1path1');
lead1p1.setAttribute("d",scope.points1);
var updateMe = function(){
console.log("Update me triggered!");
scope.advancePoints(scope.points1)
};
var tl = new TimelineMax({repeat:-1, paused:true});
tl.to(lead1p1,.1, {attr:{d:scope.points1}, onComplete:scope.advance, onCompleteParams:[{id:1}]})
.to(lead1p1,.1, {attr:{d:scope.points2}, onComplete:scope.advance, onCompleteParams:[{id:2}]})
.to(lead1p1,.1, {attr:{d:scope.points3}, onComplete:scope.advance, onCompleteParams:[{id:3}]});
document.getElementById('tweenStarter').addEventListener('click', function(){tl.play();});
document.getElementById('tweenStopper').addEventListener('click', function(){tl.pause();});
var style = window.getComputedStyle(lead1p1);
var left = style.getPropertyValue('left');
};
return {
scope:{
points1: '@points1',
points2: '@points2',
points3: '@points3',
advance: '&advance'
},
link: linker,
templateUrl: 'pages/svg-directive.html'
}
}]);
services.js
cacheTestApp.service('CacheService', ['$rootScope',function(){
/**
* Uses points from points js to simulate return to points request.
* @returns {pointsServicePoints}
*/
this.extractDataPoints = function(){
return pointsServicePoints;
};
this.extractHeartbeats = function(){
};
}]);
/**
* PointConverterService
* Converts from x,y to svg.
* Returns path.
*/
cacheTestApp.service('PointConverterService', [function(){
this.convertToSvg = function(pointArray){
var size = pointArray.length;
var last = size - 4;
var path = "M" + [pointArray[0], pointArray[1]];
for (var i = 0; i < size - 2; i += 2) {
var x0 = i ? pointArray[i - 2] : pointArray[0];
var y0 = i ? pointArray[i - 1] : pointArray[1];
var x1 = pointArray[i + 0];
var y1 = pointArray[i + 1];
var x2 = pointArray[i + 2];
var y2 = pointArray[i + 3];
var x3 = i !== last ? pointArray[i + 4] : x2;
var y3 = i !== last ? pointArray[i + 5] : y2;
var cp1x = (-x0 + 6 * x1 + x2) / 6;
var cp1y = (-y0 + 6 * y1 + y2) / 6;
var cp2x = (x1 + 6 * x2 - x3) / 6;
var cp2y = (y1 + 6 * y2 - y3) / 6;
path += "C" + [cp1x, cp1y, cp2x, cp2y, x2, y2];
}
return path;
};
}]);
svg-directive.html
<svg id="ekg_holder">
<defs>
<pattern id="basicPattern" x="0" y="0" width="5.5" height="5.5" patternUnits="userSpaceOnUse">
<rect x='0' y='0' height="5.5" width="5.5" style="fill:rgb(250,250,255);stroke-width:.5;stroke:rgb(0,0,0)"></rect>
</pattern>
</defs>
<rect x="0" y="0" width="1100" height="220" fill="url(#basicPattern)"/>
<rect x='0' y='110' height="1" width="100%"
style="fill:rgb(0,0,0);stroke-width:.1;stroke:rgb(0,0,0)"/>
<!--<polyline id="EKGpoly"></polyline>-->
<path id="lead1path1" class="ekgMove"/>
<!-- <path id="lead1path2" class="ekgMove"/>
<path id="lead1path3" class="ekgMove"/>-->
</svg>
<a href="#" id="tweenStarter">Start</a>
<a href="#" id="tweenStopper">Stop</a>
{{points1}}
index.html
<!DOCTYPE html>
<html lang="en-us" data-ng-app="cacheTestApp">
<head>
<title>Cache Test App</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-COMPATIBLE" content="IE-Edge">
<link rel="stylesheet" href="css/bootstrap.min.css"/>
<link rel="stylesheet" href="css/css.css"/>
<script src="js/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/services.js"></script>
<script src="js/directives.js"></script>
<script src="js/greensock/uncompressed/TweenMax.js"></script>
<style>
html, body, input, select, textarea {
font-size: 1.05em !important;
}
</style>
</head>
<body data-ng-controller="CacheTestAppController as cacheCont">
<div class="container">
<div svg-directive ng-if="dPoints1" points1="{{dPoints1}}" points2="{{dPoints2}}" points3="{{dPoints3}}"
advance="advancePoints(id)"></div>
</div>
<div class="container">
<div>
<div id="var1holder" class="jumbotron" data-ng-bind="dPoints1"></div>
<div id="var2holder" class="jumbotron" data-ng-bind="dPoints2"></div>
</div>
</div>
</body>
</html>
css :
#lead1path1{
fill: none;
stroke: #ed0002;
stroke-width: 1;
}
Thanks in advance for any help, and thanks to @OSUblake for the help on the x,y -> svg conversion!