AngularJS animate image on src change - angularjs

I have an AnuglarJS app, where I load/change some images from a webservice...
Controller
.controller('PlayerCtrl', function($scope, programService) {
....
programService.refresh(function(data) {
$scope.program = data;
});
....
Template
<img src="{{program.image}}" />
When my app updates from the webservice the images changes as expected, I just want to make an fadeout / fadein when this happens, how can that be done?
Is it possible to always make a fadeout/in when a image src changes?

Thanks for the responses -
I ended up doing this, and it works ;)
--- Directive ---
.directive('fadeIn', function($timeout){
return {
restrict: 'A',
link: function($scope, $element, attrs){
$element.addClass("ng-hide-remove");
$element.on('load', function() {
$element.addClass("ng-hide-add");
});
}
};
})
--- Template ---
<img ng-src="{{program.image}}" class="animate-show" fade-in />
--- CSS ---
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
transition: all linear 0.5s;
display: block !important;
}
.animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-remove {
opacity: 0;
}
.animate-show.ng-hide-add, .animate-show.ng-hide-remove.ng-hide-remove-active {
opacity: 1;
}

Update 1.5.x - with Angular 1.5.x you can use ngAnimateSwap to achieve this effect.

Based on pkdkk's answer and the Angular.js 1.3.6 sources, my solution is as such (the CSS animation part is as used for standard ngShow):
// Copied from the Angular's sources.
var NG_HIDE_CLASS = 'ng-hide';
var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
app.directive('myFadeIn', function($animate, $timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs){
element.addClass("ng-hide");
element.on('load', function() {
$timeout(function () {
$animate.removeClass(element, NG_HIDE_CLASS, {
tempClasses: NG_HIDE_IN_PROGRESS_CLASS
});
});
});
}
}
});

As christoph has mentioned, you should watch using $watch on the image source change.
But first make sure you use the ng-src rather than the src for the image tag.
<image id="new" ng-src="program.image" />
$scope.$watch('program.image', function(newValue, oldValue) {
if(newValue===oldValue) return;
$('img#new').hide();
$('img#new').fadeIn("slow", function() {});
})

In case others end up here wanting to perform animations on change of a background image, I'll post what I ended up using.
This directive assumes it's attached to a template like this:
<!-- Full screen background image and scarecrow for onload event-->
<div class="full-screen-image" data-background-image="{{backgroundImageUrl}}"></div>
<img class="hidden-full-screen-image hidden" data-ng-src="{{backgroundImageUrl}}"></div>
We want to set the background image source for the <div>, but attach an onload event so we know when the new image has arrived. To do that, we use an <img> with a .hidden class that has .hidden {display: none;}. Then we use the following directive to dynamically set the div's background image source and perform a fade to white then back from white on image change:
/***
*
* Directive to dynamically set background images when
* controllers update their backgroundImageUrl scope
* variables
*
* Template: <div data-background-image="{{backgroundImageUrl}}" />
* AND <img data-background-image="{{backgroundImageUrl}}" class="image-onload-target hidden" />
*
***/
var angular = require('angular');
angular.module('BackgroundImage', [])
.directive('backgroundImage', [
"$timeout",
function ($timeout) {
return function(scope, element, attrs){
attrs.$observe('backgroundImage', function(value) {
/***
*
* Define a callback to trigger once the image loads.
* The value provided to this callback = the value
* passed to attrs.$observe() above
*
***/
var imageLoadedCallback = function(value) {
// once the image load event triggers, remove the event
// listener to ensure the event is called only once
fadeOut();
target.removeEventListener('load', imageLoadedCallback);
$timeout(function() {
fadeIn(value);
}, 700);
}
/***
*
* Define fade in / out events to be called once a new image
* is passed to the attrs.backgroundImage in the directive
*
***/
var fadeOut = function() {
element.css({'opacity': '0'})
};
var fadeIn = function(value) {
element.css({
'background': 'url(' + value +') no-repeat center center fixed',
'background-size' : 'cover',
'opacity': '1'
});
};
// add an onload event to the hidden-full-screen-image
var target = document.querySelector('.image-onload-target');
target.addEventListener('load', imageLoadedCallback(value));
});
};
}]);
Working with Angular makes me love React all the more...

I know its late but according to #Aides answer i am posting here an working example that how can you achieve animation with change in ng-src using ngAnimateSwap (with Angular 1.5.x). I hope this helps someone in future:
HTML Markup:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-ngAnimateSwap-directive-production</title>
<link href="animations.css" rel="stylesheet" type="text/css">
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<script src="//code.angularjs.org/snapshot/angular-animate.js"></script>
<script src="script.js"></script>
<script type="text/javascript">
angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
</script>
</head>
<body ng-app="ngAnimateSwapExample" ng-controller="AppCtrl">
<div class="container">
<img ng-animate-swap="activeImage" class="cell swap-animation" ng-src="{{activeImage}}" alt="My Active Image" />
</div>
<div>
Current Image: {{activeImage}}
<br />
<button ng-click="previous()">Previous</button>
<button ng-click="next()">Next</button>
</div>
</body>
</html>
JS (script.js):
(function(angular) {
'use strict';
angular.module('ngAnimateSwapExample', ['ngAnimate'])
.controller('AppCtrl', ['$scope', '$timeout', function($scope, $timeout) {
var baseUrl = "http://lorempixel.com/400/200/sports";
$scope.images = [];
$scope.startIndex = 0;
for (var i = 0; i < 5; i++) {
$scope.images.push(baseUrl + "/" + i);
}
$scope.activeImage = $scope.images[$scope.startIndex];
/*
$interval(function() {
$scope.startIndex++;
if($scope.images[$scope.startIndex] && $scope.images[$scope.startIndex] != undefined){
$scope.activeImage = $scope.images[$scope.startIndex];
}
}, 2000);
*/
$scope.previous = function() {
$scope.startIndex--;
$timeout(function() {
if ($scope.images[$scope.startIndex] && $scope.images[$scope.startIndex] !== undefined) {
$scope.activeImage = $scope.images[$scope.startIndex];
}
}, 500);
};
$scope.next = function() {
$scope.startIndex++;
$timeout(function() {
if ($scope.images[$scope.startIndex] && $scope.images[$scope.startIndex] !== undefined) {
$scope.activeImage = $scope.images[$scope.startIndex];
}
}, 500);
};
}]);
})(window.angular);
Working plunker here.

My solution to this problem is to watch for changes on ng-src and using a timeout function to add a class which does the fadeIn effect.
HTML
<img ng-src="your-logic-will-go-here" class="animate-show ng-hide-add" fade-in>
Angular Code
.directive('fadeIn', function($timeout){
return {
restrict: 'A',
link: function($scope, $element, attrs){
$scope.$watch('selectedFormat.name', function(newValue, oldValue) {
if(newValue!=oldValue) {
$element.removeClass("ng-hide-add");
$element.addClass("ng-hide-remove");
$timeout(function () {
$element.addClass("ng-hide-add");
}, 100);
}
})
}
};
})
CSS
.animate-show.ng-hide-add, .animate-show.ng-hide-remove {
display: inline-block !important;
}
.animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-remove {
opacity: 0;
}
.animate-show.ng-hide-add{
transition: all linear 0.7s;
}
.animate-show.ng-hide-add, .animate-show.ng-hide-remove.ng-hide-remove-active {
opacity: 1;
}

You can't animate an img src change. You can, however, have multiple images and animate their opacity.
HTML/angular template
<div class="image-container">
<img src="image-one.jpg" ng-show="showImageOne">
<img src="image-two.jpg" ng-show="showImageTwo">
</div>
CSS
.image-container {
position: relative;
}
.image-container img {
position: absolute;
transition: 1s opacity linear;
}
.image-container img.ng-hide {
display: block!important;
opacity: 0;
}

Related

Merge two examples in Angularjs

I am trying to build #TagFriends and comment box feature from FACEBOOK.
I found two examples that might help, I am having library issue when I import one examples library in second one, whole code fails to work, if they are executed individually they work correctly.
1.Simple comment box :
http://devzone.co.in/angularjs-example-simple-user-comment-box/
2.#Tag friend code Below:
<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap#*" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
<link rel="stylesheet" href="http://urbanoalvarez.es/smart-area/dist/smart-area.css">
<script data-require="jquery#2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script data-require="bootstrap#*" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script data-require="angular.js#1.3.8" data-semver="1.3.8" src="https://code.angularjs.org/1.3.8/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-sanitize.js"></script>
<script src="http://urbanoalvarez.es/smart-area/dist/smart-area.js"></script>
<script src="elastic.js"></script>
<script src="app.js"></script>
<style>
.user{
color: #0074D9;
}
</style>
</head>
<body ng-app="myApp">
<div class="container" ng-controller="DemoController">
<h3>MOC 12</h3>
<!--<h4>#user mentions</h4>-->
<textarea class="form-control code" rows="5" ng-model="text" ng-trim="false" msd-elastic smart-area="config"></textarea>
<hr>
<small class="text-muted">
<b>Available users:</b><br> Bret, Antonette, Samantha, Karianne, Kamren, Leopoldo_Corkery, Elwyn.Skiles, Delphine, Maxime_Nienow, Moriah.Stanton <br>
Type for example "Hey #Antonette"
</div>
<script>
/*
* angular-elastic v2.4.2
* (c) 2014 Monospaced http://monospaced.com
* License: MIT
*/
angular.module('myApp', ['smartArea', 'monospaced.elastic'])
.controller('DemoController', ['$scope', '$http', function($scope, $http) {
$scope.text = '';
$scope.config = {
autocomplete: [
{
words: [/#([A-Za-z]+[_A-Za-z0-9]+)/gi],
cssClass: 'user'
}
],
dropdown: [
{
trigger: /#([A-Za-z]+[_A-Za-z0-9]+)/gi,
list: function(match, callback){
// match is the regexp return, in this case it returns
// [0] the full match, [1] the first capture group => username
$http.get('http://jsonplaceholder.typicode.com/users')
.success(function(data){
// Prepare the fake data
var listData = data.filter(function(element){
return element.username.substr(0,match[1].length).toLowerCase() === match[1].toLowerCase()
&& element.username.length > match[1].length;
}).map(function(element){
return {
display: element.username, // This gets displayed in the dropdown
item: element // This will get passed to onSelect
};
});
callback(listData);
}).error(function(err){
console.error(err);
});
},
onSelect: function(item){
return item.display;
},
mode: 'replace'
}
]
};
}]);
angular.module('monospaced.elastic', [])
.constant('msdElasticConfig', {
append: ''
})
.directive('msdElastic', [
'$timeout', '$window', 'msdElasticConfig',
function($timeout, $window, config) {
'use strict';
return {
require: 'ngModel',
restrict: 'A, C',
link: function(scope, element, attrs, ngModel) {
// cache a reference to the DOM element
var ta = element[0],
$ta = element;
// ensure the element is a textarea, and browser is capable
if (ta.nodeName !== 'TEXTAREA' || !$window.getComputedStyle) {
return;
}
// set these properties before measuring dimensions
$ta.css({
'overflow': 'hidden',
'overflow-y': 'hidden',
'word-wrap': 'break-word'
});
// force text reflow
var text = ta.value;
ta.value = '';
ta.value = text;
// exit if elastic already applied (or is the mirror element)
if ($ta.data('elastic')) {
return;
}
// Opera returns max-height of -1 if not set
maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
// append mirror to the DOM
if (mirror.parentNode !== document.body) {
angular.element(document.body).append(mirror);
}
// set resize and apply elastic
$ta.css({
'resize': (resize === 'none' || resize === 'vertical') ? 'none' : 'horizontal'
}).data('elastic', true);
/*
* methods
*/
/*
* initialise
*/
// listen
if ('onpropertychange' in ta && 'oninput' in ta) {
// IE9
ta['oninput'] = ta.onkeyup = adjust;
} else {
ta['oninput'] = adjust;
}
$win.bind('resize', forceAdjust);
scope.$watch(function() {
return ngModel.$modelValue;
}, function(newValue) {
forceAdjust();
});
scope.$on('elastic:adjust', function() {
initMirror();
forceAdjust();
});
$timeout(adjust);
/*
* destroy
*/
scope.$on('$destroy', function() {
$mirror.remove();
$win.unbind('resize', forceAdjust);
});
}
};
}
]);
</script>
</body>
</html>
I have implemented this feature. Find the GitHub: github.com/noob93/Smart-Comment-Box
The Devzone example is using a deprecated feature of AngularJs, I modified the code and feature started working fine.

How to write directive to hide div clicking on it or anywhere on page?

I am writing directive for first time.What i am trying to do is when user click on div it open that div, if div is opened and user click anywhere on page to close that div and if div is closed and user click anywhere on page that div stay closed. My html looks like this:
<div id="loggedIn" close-logged-in class="fade-show-hide" ng-show="loggedInOpened" default-display='block' ng-cloak>
#Html.Partial("~/Views/Shared/_LoggedInPartial.cshtml")
</div>
My angular:
$scope.toggleLoggedIn = function () {
$scope.loggedInOpened = !$scope.loggedInOpened;
$scope.languagesOpened = false;
$scope.loginOpened = false;
};
And my directive looks like this:
'use strict';
angular.module("mainModule").directive('closeLoggedIn',['$window', function ($window) {
return {
// bind a local scope (i.e. on link function scope) property
// to the value of default-display attribute in our target <div>.
scope: {
defaultDisplay: '#'
},
restrict: 'A',
link: function (scope, element, attrs) {
var el = element[0];
el.style.display = scope.defaultDisplay || 'block';
angular.element($window).bind('click', function(){
if(scope.defaultDisplay == 'block')
el.style.display = 'none';
});
}
};
}]);
Any suggestion?
<div ng-controller="MyCtrl" ng-show="userclick==1" class="sample">
<div class="test" ng-hide="hideDiv" > hideDiv </div>
</div>
Use ng-show
modify the value of var userclick to zero on whatever condition you mentioned and the div will be hidden till the userclick value is not 1.
Be sure the the value is getting applied updated.
print the updated value to console.
use
$scope.$apply(function () {
$scope.userclick = 0;
});
As my understanding I have created a sample structure it might be help full for you please check..
HTML:
<div ng-controller="MyCtrl" ng-click="testing() "class="sample">
<div class="test" ng-hide="hideDiv" > hideDiv </div>
</div>
JS:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.hideDiv = false;
$scope.testing = function () {
if ($scope.hideDiv) {
$scope.hideDiv = false;
} else {
$scope.hideDiv = true;
}
}
}
CSS:
.sample{
background-color: red;
height:100px;
width: 100%;
}

Heatmap.js directive for Angularjs

is there a angularJS directive for heatmap.js?
Can't find anything and can't get it to work
Thanks
= Edit =
I get this error whether I used my code or the one below (both work). My problem was actually the version of the heatmap.js that that I was using from the bower. When I download the min.js used in the fiddle it all works fine.
TypeError: Cannot read property 'style' of null
at Object.heatmap.resize (http://localhost:56080/app/bower_components/heatmap.js/src/heatmap.js:363:74)
at Object.heatmap.init (http://localhost:56080/app/bower_components/heatmap.js/src/heatmap.js:386:20)
at Object.heatmap (http://localhost:56080/app/bower_components/heatmap.js/src/heatmap.js:331:14)
at Object.heatmapFactory.create (http://localhost:56080/app/bower_components/heatmap.js/src/heatmap.js:627:24)
at link (http://localhost:56080/app/js/directives/MainDirective.js:9:36)
Simple wrapper directive for heatmap.js
HTML
<div ng-app="myapp">
<div ng-controller="MyCtrl1">
<heat-map data="passed_data"></heat-map>
</div>
</div>
JS
var myApp = angular.module('myapp',[]);
myApp
.controller('MyCtrl1', function ($scope) {
// now generate some random data
var points = [];
var max = 0;
var width = 840;
var height = 400;
var len = 200;
while (len--) {
var val = Math.floor(Math.random()*100);
max = Math.max(max, val);
var point = {
x: Math.floor(Math.random()*width),
y: Math.floor(Math.random()*height),
value: val
};
points.push(point);
}
// heatmap data format
$scope.passed_data = {
max: max,
data: points
};
})
.directive('heatMap', function(){
return {
restrict: 'E',
scope: {
data: '='
},
template: '<div container></div>',
link: function(scope, ele, attr){
scope.heatmapInstance = h337.create({
container: ele.find('div')[0]
});
scope.heatmapInstance.setData(scope.data);
}
};
});
CSS
heat-map {
width: 840px;
height: 400px;
display: block;
}
heat-map div {
height: 100%;
}
JsFiddle - http://jsfiddle.net/jigardafda/utjjatuo/2/
heatmap.js example reference link
http://www.patrick-wied.at/static/heatmapjs/example-minimal-config.html
jad-panda's answer (https://stackoverflow.com/a/30193896/3437606) is really helpfull.
But if you don't want to make the size of the heatmap hardcoded in css and apply them dynamicaly with ng-style, you have to make the following minor changes.
HTML
<div ng-style="heatMapStyle">
<heat-map data="passed_data"></heat-map>
</div>
Controller
just add the style object to the $scope like
$scope.heatMapStyle = {
"height": 100+ "px",
"width": 150+ "px"
};
The rest of the controler is the same as in jad-panda's answer.
Directive
.directive('heatMap', ['$timeout', function ($timeout) {
return {
restrict: 'E',
scope: {
data: '='
},
template: '<div container></div>',
link: function (scope, ele, attr) {
function init() {
scope.heatmapInstance = h337.create({
container: ele.find('div')[0]
});
scope.heatmapInstance.setData(scope.data);
}
//to ensure that the wrapping style is already applied
$timeout(init,0);
}
};
}])
The $timout is essential to ensure that the heatmap is initialized in the next digestcycle of AngularJs when the ng-styleis already applied.
CSS
And last the new CSS:
heat-map {
position: relative;
width: 100%;
height: 100%;
}
heat-map div {
height: 100%;
}
Just found an oficial wrapper for heatmap.js, hosted in the same github repository.
It can be downloaded from: https://github.com/pa7/heatmap.js/blob/master/plugins/angular-heatmap/angular-heatmap.js
And it's explained here:
https://www.patrick-wied.at/static/heatmapjs/plugin-angular-heatmap.html

Angular JS animation not working, (using ngAnimate and TweenMax)

ok, here is my code:
(function(){
//Define angular main module - module -
var app = angular.module('module', ['ngAnimate']);
//Define controller -TimeLine-
app.controller('TimeLine', function(){
this.initialize = true;
});
//Define controller -PersonalGravatar-
app.controller('PersonalGravatar', function(){
this.email = "mail#gmail.com";
this.getImage = function(email) {
// MD5 (Message-Digest Algorithm) by WebToolkit
var size = size || 460;
return 'http://www.gravatar.com/avatar/' + MD5(email) + '.jpg?s=' + size;
};
});
//Define animation - gravatar-
app.animation(".gravatar", function() {
console.log("im displaying correctly");
//that's it, this next 'return' is not working.
return {
console.log("im NOT displaying in console");
enter: function(element, done){
TweenMax.to(element, 2, { css:{left:500, onComplete:done} } );
},
leave: function(element, done){
// TweenMax.to(element, 2, { css:{left:500, onComplete:done} } );
}
};
});
})();
then my html code is:
<html class="no-js" ng-app="module">
... more code ...
<div id="app-main-container" ng-controller="TimeLine as animations">
<div id="gravatar" class="gravatar" ng-controller="PersonalGravatar as gravatar"
ng-if="animations.initialize">
<img ng-src="{{gravatar.getImage(gravatar.email)}}" alt="">
</div>
</div> <!-- /app main container -->
<script src="../1.2.18/angular.min.js"></script>
<cript src="../1.2.18/angular-animate.min.js"> </script>
<script src="../1.12.1/TweenMax.min.js"></script>
<script src="js/main.js"></script>
so I'm new in angularJS, I don't no why app.animation is not returning any animation.. thanks a LOT!!
Just $timeout...
app.controller('TimeLine', function($scope, $timeout){
return $timeout(function() {
$scope.initialize = true;
}, 100);
});
I created a Plunk of your example animating the enter and leave.
Notice how you no longer need to wrap your CSS properties in a css object.
enter: function(element, done){
TweenMax.from(element, 1, {x:500, autoAlpha: 0, scale: 0.5, onComplete:done});
},
leave: function(element, done){
TweenMax.to(element, 1, {x:500, autoAlpha: 0, scale: 0.5, onComplete:done});
}
Plunker http://plnkr.co/edit/6tQFdA?p=preview

replace jQuery animation with built in angular animation

Given this template:
<div fade>
<h2>
TEST {{ headline.Title }}
</h2>
</div>
And the following directive:
How do I change this directive to replace the jquery fade with built in angular animations?
I require the text to fade out, get replaced, and then fade in.
newman.directive('fade', ['$interval', function($interval) {
return function ($scope, element, attrs) {
$scope.index = 0;
$scope.news = $interval(function () {
// REPLACE JQUERY BELOW
$(element).fadeOut('fast', function() {
$scope.index = $scope.getValidNewHeadlineIndex();
// view is currently correctly updated by the line below.
$scope.headline = $scope.headlines[$scope.index];
$(element).fadeIn('slow'); // REPLACE JQUERY HERE TOO!
});
}, 10000);
}
}]);
Figured it out, mostly...
This is for anyone else battling with angular-js animation. A working CODEPEN.
The basic procedure is to create some CSS to create the animation, and add a call to $animate.enter(... to the 'fade' directive.
$animate.leave doesn't seem to be required. I will add more detail when I know more.
the modified directive:
app.directive('fade', ['$animate', '$interval', function($animate, $interval) {
return function ($scope, element, attrs) {
$interval(function () {
$animate.enter(element, element.parent());
$scope.headline = $scope.next();
/* $animate.leave(element); */ // not required?
}, 6000);
}
}]);
the style sheet entries:
.fade {
transition: 2s linear all;
-webkit-transition: 2s linear all;
}
.fade.ng-enter {
opacity: 0;
}
.fade.ng-enter.ng-enter-active {
opacity: 1;
}
alternate solution, using TweenMax
This solution is suitable for (you guessed it - internet explorer < 10)
TweenMax solution using onComplete.
app.directive('fade', ['$animate', '$interval', function($animate, $interval) {
var fadeOut = function(target, done){
TweenMax.to(
target, 0.2,/*{'opacity':'1',ease:Ease.linear},*/
{'opacity':'0',ease:Ease.linear, onComplete:done });
};
var fadeInUp = function(target){
var tl = new TimelineMax();
tl.to(target,0,{'opacity':'0',top:'+=50'})
.to(target,1,{'opacity':'1',top:'-=50',ease:Quad.easeOut});
};
return function ($scope, element, attrs) {
$interval(function () {
fadeOut(element, function() {
$scope.$apply(function() {
$scope.headline = $scope.next();
fadeInUp(element);
});
});
}, 4000);
}
}]);

Resources