Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Code+Pro">
  <link rel="stylesheet" href="circles.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.rawgit.com/lodash/lodash/3.0.1/lodash.min.js"></script>
  <script src="https://code.highcharts.com/highcharts.js"></script>
  <script src="https://code.highcharts.com/modules/exporting.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react-with-addons.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.0/react-dom.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
  <script src="https://unpkg.com/can@3.2.1/dist/global/can.all.js"></script>
</head>
<body>
  <div class="intro">
    <p>Run the test below to see how DoneJS' view engine beats the competition by performing minimal DOM updates when properties are changed.</p>
    <p>The test will run 1000 updates in each framework and record how long each update takes. Each update will animate the circles, change their text and background color.</p>
    </div>
  <button class="animate" onclick="runTest()">Run Test</button>
  <p id="timing"></p>
  <div style="display: none;" id="testResults">
      <p>The most performant approach is to touch the DOM as little as possible.Angular performs the worst because there are a large number of objects to watch while React is closer with its diffing approach.</p>
      <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
    </div>
  <div id="grid"></div>
  <div ng-app="animationApp">
    <div ng-controller="animationCtrl">
     <div class="grid">
       <div class="box-view" ng-repeat="box in boxes">
         <div class="box" ng-style="{top: box.top+'px', left: box.left+'px', background: 'rgb(0,0,'+box.color+')'}">{{box.content}}</div>
       </div>
      </div>
    </div>
  </div>
  <script type="text/stache" id="stache-template">
    {{#each boxes}}
      <div class="box-view">
        <div class="box" id="{{number}}" style="{{style}}">
          {{content}}
        </div>
      </div>
    {{/each}}
  </script>
</body>
</html>
 
body {
  font-family: "Lato", sans-serif;
  background: #FCFEFD;
  padding: 5px;
}
p {
  font-size: 16px;
  line-height: 24px;
  font-weight: 400;
  color: #424a4e;
}
.animate, .reset {
  border: none;
  cursor: pointer;
  width: 100%;
    color: #fff; 
    height: 35px;
    padding: 5px;
    display: block;
    margin: 0 auto;
      line-height: 1.5em;
  background: #3B83A3;
      font-weight: 700;
  font-size: 14px;
}
.reset {
  background: #393E40 ;
}
button {
  font: bold 14px/14px Lato;  
  margin-left: 10px;
}
#grid, .grid {
  margin: 10px;   
}
.box-view {
  width: 20px; height: 20px;
  float: left;
  position: relative;
  margin: 8px;    
}
.box {
  border-radius: 100px;
  width: 20px; height: 10px;
  padding: 5px 0;
  color: #fff;
  font: 10px/10px Arial;
  text-align: center;
  position: absolute;
}   
 
/* globals _ */
// noprotect
var N = 100; //Number of circles
var L = 1000; //Number of loops to run for each library
//CanJS
(function() {
    var boxes = new can.DefineList();
    var Box = can.DefineMap.extend({
        count: { value: 0 },
        top: { value: 0 },
        left: { value: 0 },
        color: { value: 0 },
        content: { value: 0 },
        style: { value: '' },
        tick: function() {
            var count = this.count + 1;
            this.count = count;
            this.top = Math.sin(count / 10) * 10;
            this.left = Math.cos(count / 10) * 10;
            this.color = count % 255;
            this.content = count % 100;
            this.style = this.computeStyle();
        },
        computeStyle: function() {
            return 'top: ' + this.top + 'px; left: ' + this.left + 'px; background: rgb(0,0,' + this.color + ');';
        }
    });
    for (var i = 0; i < 100; i++) {
        var box = new Box({ number: i });
        box.tick();
        boxes.push(box);
    }
    var canAnimate = function() {
        can.batch.start();
        for (var i = 0; i < boxes.length; i++) {
            boxes[i].tick();
        }
        can.batch.stop();
    };
    window.runCan = function() {
        window.reset();
        window.currentBenchmark = 'can';
        var template = document.getElementById('stache-template').innerHTML;
        $('#grid').append(can.stache(template)({
            boxes: boxes
        }));
        window.benchmarkLoop(canAnimate);
    };
})();
//React
/* globals React, ReactDOM */
(function() {
    var counter;
    var BoxView = React.createClass({
        render: function() {
            var count = this.props.count + 1;
            return React.DOM.div({ className: "box-view" }, React.DOM.div({
                className: "box",
                style: {
                    top: Math.sin(count / 10) * 10,
                    left: Math.cos(count / 10) * 10,
                    background: 'rgb(0, 0,' + count % 255 + ')'
                }
            }, count % 100));
        }
    });
    var BoxesView = React.createClass({
        render: function() {
            var boxes = _.map(_.range(N), function(i) {
                return React.createFactory(BoxView)({ key: i, count: this.props.count });
            }, this);
            return React.DOM.div(null, boxes);
        }
    });
    var reactInit = function() {
        counter = -1;
        reactAnimate();
    };
    var reactAnimate = function() {
        ReactDOM.render(
            React.createFactory(BoxesView)({ count: counter++ }),
            document.getElementById('grid')
        );
    };
    window.runReact = function() {
        window.reset();
        window.currentBenchmark = 'react';
        reactInit();
        window.benchmarkLoop(reactAnimate);
    };
})();
//Angular
/* global angular */
(function() {
    var animationApp = angular.module('animationApp', []);
    animationApp.controller('animationCtrl', function($scope, $rootScope, $window) {
        $scope.count = 0;
        $scope.boxes = new Array();
        $scope.Box = function(n) {
            this.number = n;
            this.top = 0;
            this.left = 0;
            this.color = 0;
            this.content = 0;
            this.count = 0;
            this.tick = function() {
                var count = this.count += 1;
                this.top = Math.sin(count / 10) * 10;
                this.left = Math.cos(count / 10) * 10;
                this.color = count % 255;
                this.content = count % 100;
            };
        };
        $scope.angularjsInit = function() {
            for (var i = 0; i < window.N; i++) {
                $scope.boxes[i] = new $scope.Box(i);
            }
        };
        $scope.angularjsAnimate = function() {
            for (var i = 0; i < window.N; i++) {
                $scope.boxes[i].tick();
            }
            $rootScope.$apply();
        };
        $window.angularReset = function() {
            $scope.boxes = new Array();
            $rootScope.$apply();
        };
        $window.runAngularjs = function() {
            $scope.boxes = new Array();
            $window.reset();
            $window.currentBenchmark = 'angular';
            $scope.angularjsInit();
            window.benchmarkLoop($scope.angularjsAnimate);
        };
    });
})();
window.runTest = function() {
    $('#testResults').hide();
    $('#timing').text('').show();
    window.runCan();
};
window.reset = function() {
    $('#grid').empty();
    window.angularReset();
    clearTimeout(window.timeout);
    window.updateResults();
    window.currentBenchmark = null;
    window.totalTime = null;
    window.loopCount = null;
};
window.timeResults = {
    can: null,
    react: null,
    angular: null
};
window.updateResults = function() {
    if (window.currentBenchmark !== null) {
        window.timeResults[window.currentBenchmark] = (window.totalTime / window.loopCount).toFixed(2);
    }
};
window.timeout = null;
window.totalTime = null;
window.loopCount = null;
window.benchmarkLoop = function(fn) {
    var startDate = new Date();
    fn();
    var endDate = new Date();
    window.totalTime += endDate - startDate;
    window.loopCount++;
    if (window.loopCount % 20 === 0) {
        $('#timing').text(
            window.currentBenchmark.charAt(0).toUpperCase() + window.currentBenchmark.slice(1) +
            ': Performed ' + window.loopCount + ' updates in ' + window.totalTime + ' ms (average ' +
            (window.totalTime / window.loopCount).toFixed(2) + ' ms per update).'
        );
    }
    if (window.loopCount < L) {
        window.timeout = _.defer(window.benchmarkLoop, fn);
    }
    else {
        if (window.currentBenchmark === 'can') {
            window.runReact();
        }
        else if (window.currentBenchmark === 'react') {
            window.runAngularjs();
        }
        else {
            window.reset();
            window.renderGraph();
        }
    }
};
window.renderGraph = function() {
    console.log([ +window.timeResults.can, +window.timeResults.react, +window.timeResults.angular ]);
    $('#testResults').show();
    $('#timing').hide();
    $('#container').highcharts({
        chart: {
            type: 'bar'
        },
        plotOptions: {
            bar: {
                dataLabels: {
                    enabled: true
                }
            }
        },
        colors: [ '#3B83A3' ],
        tooltip: {
            valueSuffix: 'ms'
        },
        title: {
            text: null
        },
        xAxis: {
            categories: [ 'CanJS', 'React', 'Angular' ],
            title: {
                text: null
            }
        },
        yAxis: {
            title: {
                text: 'Time per update (ms)'
            },
            plotLines: [ {
                value: 0,
                width: 1,
                color: '#808080'
            } ]
        },
        legend: {
            enabled: false
        },
        series: [ {
            data: [ +window.timeResults.can, +window.timeResults.react, +window.timeResults.angular ]
        } ]
    });
};
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
christopherjbakerpro
0viewers