From 875af37b35d6f1f30624a5f6c202d7458b80779d Mon Sep 17 00:00:00 2001 From: Paolo Date: Sun, 13 Mar 2016 14:25:26 +0100 Subject: [PATCH 01/11] Remove spy and use angular-spy in the example --- bower.json | 3 +- example/demo/cat-class.html | 1 + example/demo/cat-delay.html | 1 + example/demo/cat-timeline.html | 1 + example/demo/scroll-spy.html | 59 ----- example/demo/scrollspy-animation.html | 7 +- example/main.js | 2 +- src/js/cat.js | 4 +- .../scroll-spy/scroll-container.directive.js | 107 ---------- src/js/scroll-spy/scroll-spy.module.js | 13 -- src/js/scroll-spy/visible.directive.js | 66 ------ src/js/utils/debounce.service.js | 31 --- src/js/utils/window-scroll-helper.service.js | 21 -- .../scroll-container.directive.spec.js | 70 ------ test/scroll-spy/scroll-spy.module.spec.js | 11 - test/scroll-spy/visible.directive.spec.js | 201 ------------------ 16 files changed, 11 insertions(+), 587 deletions(-) delete mode 100644 example/demo/scroll-spy.html delete mode 100644 src/js/scroll-spy/scroll-container.directive.js delete mode 100644 src/js/scroll-spy/scroll-spy.module.js delete mode 100644 src/js/scroll-spy/visible.directive.js delete mode 100644 src/js/utils/debounce.service.js delete mode 100644 src/js/utils/window-scroll-helper.service.js delete mode 100644 test/scroll-spy/scroll-container.directive.spec.js delete mode 100644 test/scroll-spy/scroll-spy.module.spec.js delete mode 100644 test/scroll-spy/visible.directive.spec.js diff --git a/bower.json b/bower.json index 5784c80..40a68c7 100644 --- a/bower.json +++ b/bower.json @@ -32,6 +32,7 @@ "angular-animate": "~1.4.3" }, "devDependencies": { - "angular-mocks": "~1.4.3" + "angular-mocks": "~1.4.3", + "angular-spy": "~0.0.1" } } diff --git a/example/demo/cat-class.html b/example/demo/cat-class.html index 78e2931..13a90f1 100644 --- a/example/demo/cat-class.html +++ b/example/demo/cat-class.html @@ -6,6 +6,7 @@ Pa animations example page + - - - -
- No spied element visible -
Green section is visible: {{ spy.greenVisible || 'false' }}
-
Red section is visible: {{ spy.redVisible || 'false' }}
-
Blu section title is visible: {{ spy.blueTitleVisible || 'false' }}
-
-
-
-
-
-
-

I'm red

-
- - - diff --git a/example/demo/scrollspy-animation.html b/example/demo/scrollspy-animation.html index 3c3cf59..e85bc8c 100644 --- a/example/demo/scrollspy-animation.html +++ b/example/demo/scrollspy-animation.html @@ -6,6 +6,7 @@ Scroll spy example page + - +

Scroll down to see some awesome animation

@@ -171,7 +172,7 @@

Scroll down to see some awesome animation

-
+

Two animation in sequence, with undo set

Style }
-
+

Nested timelines!

{ - return { - restrict: 'A', - controller: ['$scope', '$element', function ($scope, $element) { - this.spies = []; - this.registerSpy = (spy) => { - this.spies.push(spy); - }; - - this.getScrollContainer = () => { - return $element[0]; - }; - }], - link (scope, elem, attrs, selfCtrl) { - const afTimeout = 400; - let vpHeight, - $aWindow = angular.element($window), - $scrollTopReference = elem[0].tagName === 'BODY' ? windowScrollGetter() : elem, - $scroller = elem[0].tagName === 'BODY' ? - $aWindow : elem, - animationFrame, - lastScrollTimestamp = 0, - scrollPrevTimestamp = 0, - previousScrollTop = 0; - - function onScroll() { - lastScrollTimestamp = $window.performance.now(); - if (!animationFrame) { - animationFrame = $window.requestAnimationFrame(onAnimationFrame); - } - scrollPrevTimestamp = $window.performance.now(); - } - - function onResize() { - vpHeight = Math.max($window.document.documentElement.clientHeight, window.innerHeight || 0); - selfCtrl.spies.forEach((spy)=> { - spy.updateClientRect(); - }); - onScroll(); - } - - function onAnimationFrame() { - const viewportRect = getViewportRect(), - timestamp = $window.performance.now(), - delta = timestamp - scrollPrevTimestamp, - scrollDelta = viewportRect.top - previousScrollTop, - scrollDirection = scrollDelta === 0 ? 0 : - scrollDelta / Math.abs(scrollDelta); - - selfCtrl.spies.forEach((spy)=> { - spy.update(viewportRect, scrollDirection); - }); - - previousScrollTop = viewportRect.top; - - if (delta < afTimeout) { - queueAf(); - } else { - cancelAf(); - } - } - - function getViewportRect() { - const currentScroll = $scrollTopReference[0].scrollTop; - return { - top: currentScroll, - height: vpHeight - }; - } - - function queueAf() { - animationFrame = $window.requestAnimationFrame(onAnimationFrame); - } - - function cancelAf() { - $window.cancelAnimationFrame(animationFrame); - animationFrame = null; - } - - scope.$on('catScrollContainer:updateSpies', onResize); - - if (angular.isDefined(attrs.triggerUpdate)) { - scope.$watch(attrs.triggerUpdate, function (newVal, oldVal) { - if (newVal !== oldVal) { - $timeout(function () { - onResize(); - }, 0); - } - }); - } - - $aWindow.on('resize', catDebounce(onResize, 300)); - $scroller.on('scroll', onScroll); - $timeout(onResize); - } - }; -}]); - -export default mod; diff --git a/src/js/scroll-spy/scroll-spy.module.js b/src/js/scroll-spy/scroll-spy.module.js deleted file mode 100644 index 8693e29..0000000 --- a/src/js/scroll-spy/scroll-spy.module.js +++ /dev/null @@ -1,13 +0,0 @@ -import scrollSpy from './scroll-container.directive'; -import spy from './visible.directive'; - -var mod = angular.module('cat.scrollSpy', [ - scrollSpy.name, - spy.name -]); - -//TODO: The current implementation works for scroll spies on the -// body element and for scroll divs when no parents are scrollable. -// The case where we have nested scroll elements has to be investigated. - -export default mod; diff --git a/src/js/scroll-spy/visible.directive.js b/src/js/scroll-spy/visible.directive.js deleted file mode 100644 index 3e2c8b7..0000000 --- a/src/js/scroll-spy/visible.directive.js +++ /dev/null @@ -1,66 +0,0 @@ -const mod = angular.module('cat.scrollSpy.visible', []); - -mod.directive('catVisible', ['$window', '$parse', '$timeout', ($window, $parse, $timeout) => { - return { - restrict: 'A', - require: '^^catScrollContainer', - link (scope, elem, attrs, ctrl) { - let rect = {}, - hidden = false, - scrollContainer, - api = { - updateClientRect () { - let clientRect = elem[0].getBoundingClientRect(); - rect.top = clientRect.top + scrollContainer.scrollTop; - rect.left = clientRect.left + scrollContainer.scrollLeft; - rect.width = clientRect.width; - rect.height = clientRect.height; - hidden = elem[0].offsetParent === null; - }, - update (viewportRect) { - let isFullyVisible = (rect.top >= viewportRect.top && //Top border in viewport - (rect.top + rect.height) <= (viewportRect.top + viewportRect.height)) || //Bottom border in viewport - (rect.top <= viewportRect.top && rect.top + rect.height >= viewportRect.top + viewportRect.height), // Bigger than viewport - - isFullyHidden = !isFullyVisible && - rect.top > (viewportRect.top + viewportRect.height) || //Top border below viewport bottom - (rect.top + rect.height) < viewportRect.top; //Bottom border above viewport top - - //Only change state when fully visible/hidden - if (isFullyVisible) { - api.setInView(true); - } else if (isFullyHidden) { - api.setInView(false); - } - }, - getRect () { - return rect; - }, - setInView (inView) { - if ($parse(attrs.catVisible)(scope) !== inView && !hidden) { - scope.$evalAsync(() => { - const catVisibleSetter = $parse(attrs.catVisible); - catVisibleSetter.assign(scope, inView); - }); - } - } - }; - if (angular.isDefined(attrs.triggerUpdate)) { - scope.$watch(attrs.triggerUpdate, function (newVal, oldVal) { - if (newVal !== oldVal) { - $timeout(function () { - api.updateClientRect(); - api.update(); - }, 0); - } - }); - } - - scrollContainer = ctrl.getScrollContainer() || $window.document.body; - ctrl.registerSpy(api); - api.updateClientRect(); - } - }; -}]); - -export default mod; diff --git a/src/js/utils/debounce.service.js b/src/js/utils/debounce.service.js deleted file mode 100644 index a77fe3f..0000000 --- a/src/js/utils/debounce.service.js +++ /dev/null @@ -1,31 +0,0 @@ -const mod = angular.module('cat.utils.debounce', []); - -mod.factory('catDebounce', ['$timeout', '$q', ($timeout, $q) => { - return (func, wait, immediate) => { - let timeout, - deferred = $q.defer(); - - return (...args) => { - const context = this, - later = () => { - timeout = null; - if (!immediate) { - deferred.resolve(func.apply(context, args)); - deferred = $q.defer(); - } - }, - callNow = immediate && !timeout; - if (timeout) { - $timeout.cancel(timeout); - } - timeout = $timeout(later, wait); - if (callNow) { - deferred.resolve(func.apply(context, args)); - deferred = $q.defer(); - } - return deferred.promise; - }; - }; -}]); - -export default mod; diff --git a/src/js/utils/window-scroll-helper.service.js b/src/js/utils/window-scroll-helper.service.js deleted file mode 100644 index 289ce0f..0000000 --- a/src/js/utils/window-scroll-helper.service.js +++ /dev/null @@ -1,21 +0,0 @@ -const mod = angular.module('cat.utils.windowScrollHelper', []); - -mod.factory('windowScrollGetter', ['$window', ($window) => { - return () => { - const docEl = $window.document.documentElement; - let scrollContainer; - - docEl.scrollTop = 1; - - if (docEl.scrollTop === 1) { - docEl.scrollTop = 0; - scrollContainer = docEl; - } else { - scrollContainer = $window.document.body; - } - - return angular.element(scrollContainer); - }; -}]); - -export default mod; diff --git a/test/scroll-spy/scroll-container.directive.spec.js b/test/scroll-spy/scroll-container.directive.spec.js deleted file mode 100644 index dde71b0..0000000 --- a/test/scroll-spy/scroll-container.directive.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import scrollSpy from '../../src/js/scroll-spy/scroll-container.directive'; - -describe('scrollSpy', () => { - let compile, - rootScope, - scope, - window, - timeout, - element, - controller, - template = ` -
- `; - - beforeEach(angular.mock.module('cat.scrollSpy.scrollContainer')); - beforeEach(angular.mock.inject(($compile, $rootScope, $timeout, $window) => { - compile = $compile; - window = $window; - rootScope = $rootScope; - scope = $rootScope.$new(); - timeout = $timeout; - element = compile(template)(scope); - controller = element.controller('catScrollContainer'); - })); - - it('should be defined', () => { - scrollSpy.should.be.defined; - }); - - it('should be an angular module', () => { - scrollSpy.name.should.equal('cat.scrollSpy.scrollContainer'); - }); - - describe('controller', () => { - it('should be defined', () => { - controller.should.be.defined; - }); - - describe('registerSpy', () => { - it('should be defined', () => { - controller.registerSpy.should.be.a('function'); - }); - - it('should register a spy', () => { - let spy = { - getRect: sinon.spy(() => { - return { - top: 500, - height: 500 - }; - }), - setInView: sinon.spy() - }; - controller.registerSpy(spy); - controller.spies.length.should.be.equal(1); - }); - }); - - describe('getScrollContainer', () => { - it('should be defined', () => { - controller.getScrollContainer.should.be.a('function'); - }); - - it('should return the scrollContainer DOM node', () => { - let node = controller.getScrollContainer(); - node.should.be.equal(element[0]); - }); - }); - }); -}); diff --git a/test/scroll-spy/scroll-spy.module.spec.js b/test/scroll-spy/scroll-spy.module.spec.js deleted file mode 100644 index 44fd78f..0000000 --- a/test/scroll-spy/scroll-spy.module.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import scrollSpyModule from '../../src/js/scroll-spy/scroll-spy.module'; - -describe('scrollSpyModule', () => { - it('should be defined', () => { - scrollSpyModule.should.be.defined; - }); - - it('should be an angular module', () => { - scrollSpyModule.name.should.equal('cat.scrollSpy'); - }); -}); diff --git a/test/scroll-spy/visible.directive.spec.js b/test/scroll-spy/visible.directive.spec.js deleted file mode 100644 index 9c65007..0000000 --- a/test/scroll-spy/visible.directive.spec.js +++ /dev/null @@ -1,201 +0,0 @@ -import visible from '../../src/js/scroll-spy/visible.directive'; - -describe('visible', () => { - let rootScope, - scope, - window, - parentElement, - element, - api, - scrollContainerController, - template = ` -
-

-
- `; - - beforeEach(angular.mock.module('cat.scrollSpy.visible')); - beforeEach(() => { - scrollContainerController = { - registerSpy: sinon.spy(), - getScrollContainer: sinon.spy(() => parentElement[0]) - }; - }); - - beforeEach(angular.mock.inject(($compile, $rootScope, $timeout, $window) => { - parentElement = angular.element(template); - parentElement.data('$catScrollContainerController', scrollContainerController); - rootScope = $rootScope; - scope = $rootScope.$new(); - $compile(parentElement)(scope); - element = parentElement.children(); - window = $window; - api = scrollContainerController.registerSpy.args[0][0]; - })); - - it('should be defined', () => { - visible.should.be.defined; - }); - - it('should be an angular module', () => { - visible.name.should.equal('cat.scrollSpy.visible'); - }); - - it('should get the scrollContainer DOM element', () => { - scrollContainerController.getScrollContainer.should.have.been.called; - }); - - it('should register itself to the parent scrollContainer directive', () => { - scrollContainerController.registerSpy.should.have.been.called; - }); - - it('should update his position on link', () => { - scrollContainerController.registerSpy.should.have.been.called; - }); - - describe('api', () => { - describe('update', () => { - it('should be a function', () => { - api.update.should.be.a('function'); - }); - - it('should check if item is fully visible', () => { - document.body.appendChild(element[0]); - element.css({ - height: '100px', - width: '300px', - display: 'block', - position: 'absolute', - top: 0, - left: 0, - margin: 0, - padding: 0 - }); - api.updateClientRect(); - sinon.spy(api, 'setInView'); - api.update({ - top: 0, - left: 0, - width: 1000, - height: 1000 - }); - api.setInView.should.have.been.calledWith(true); - }); - - it('should check if item is fully hidden', () => { - document.body.appendChild(element[0]); - element.css({ - height: '100px', - width: '300px', - display: 'block', - position: 'absolute', - left: 0, - margin: 0, - top: '1001px', - padding: 0 - }); - api.updateClientRect(); - sinon.spy(api, 'setInView'); - api.update({ - top: 0, - left: 0, - width: 1000, - height: 1000 - }); - api.setInView.should.have.been.calledWith(false); - }); - - it('should not change status if in between', () => { - document.body.appendChild(element[0]); - element.css({ - height: '1000px', - width: '300px', - display: 'block', - position: 'absolute', - left: 0, - margin: 0, - top: '500px', - padding: 0 - }); - api.updateClientRect(); - sinon.spy(api, 'setInView'); - api.update({ - top: 0, - left: 0, - width: 1000, - height: 1000 - }); - api.setInView.should.not.have.been.called; - }); - }); - - describe('updateClientRect', () => { - it('should be a function', () => { - api.updateClientRect.should.be.a('function'); - }); - - it('should update the rect object calling getBoundingClientRect', () => { - let updateRect = sinon.spy(element[0], 'getBoundingClientRect'); - api.updateClientRect(); - updateRect.should.have.been.called; - }); - }); - - describe('getRect', () => { - it('should be a function', () => { - api.getRect.should.be.a('function'); - }); - it('should be a function', () => { - api.getRect.should.be.a('function'); - }); - - }); - - describe('setInView', () => { - beforeEach(() => { - document.body.appendChild(element[0]); - api.updateClientRect(); - }); - - afterEach(() => { - document.body.removeChild(element[0]); - }); - - it('should be a function', () => { - api.setInView.should.be.a('function'); - }); - - it('should set visible to true', () => { - - api.setInView(true); - scope.$apply(); - scope.visible.should.be.true; - }); - - it('should set visible to false', () => { - api.setInView(false); - scope.$apply(); - scope.visible.should.be.false; - }); - - it('should call evalAsync id visible value changes', () => { - let spyEval = sinon.spy(scope, '$evalAsync'); - scope.visible = true; - scope.$apply(); - api.setInView(false); - scope.$apply(); - spyEval.should.have.been.called; - }); - - it('should set visible to false', () => { - let spyEval = sinon.spy(scope, '$evalAsync'); - scope.visible = false; - scope.$apply(); - api.setInView(false); - scope.$apply(); - spyEval.should.not.have.been.called; - }); - }); - }); - -}); From 1ed4c2e0ea6a25b8219ad064bed0ed58da4a48ac Mon Sep 17 00:00:00 2001 From: Paolo Date: Sun, 13 Mar 2016 14:32:21 +0100 Subject: [PATCH 02/11] Remove disable flag for nested example --- example/demo/scrollspy-animation.html | 1 - 1 file changed, 1 deletion(-) diff --git a/example/demo/scrollspy-animation.html b/example/demo/scrollspy-animation.html index e85bc8c..5e3f6de 100644 --- a/example/demo/scrollspy-animation.html +++ b/example/demo/scrollspy-animation.html @@ -241,7 +241,6 @@

Style

Nested timelines!

Date: Sun, 13 Mar 2016 23:11:30 +0100 Subject: [PATCH 03/11] Update angular-spy dependency #45 --- bower.json | 2 +- example/main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 40a68c7..8a6916b 100644 --- a/bower.json +++ b/bower.json @@ -33,6 +33,6 @@ }, "devDependencies": { "angular-mocks": "~1.4.3", - "angular-spy": "~0.0.1" + "angular-spy": "~0.0.3" } } diff --git a/example/main.js b/example/main.js index d5ad291..6d1551a 100644 --- a/example/main.js +++ b/example/main.js @@ -1,4 +1,4 @@ -var app = angular.module('example', ['cat', 'ngSpy']).run(function() { +var app = angular.module('example', ['cat', 'spy']).run(function() { console.log('I am running'); }); From 4534ab1d98f76c486249e660a087bc9f73262f54 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Tue, 15 Mar 2016 20:56:18 +0100 Subject: [PATCH 04/11] WIP - update readme #47 --- README.md | 108 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 0f5825d..733e8f7 100644 --- a/README.md +++ b/README.md @@ -25,27 +25,25 @@ A set of AngularJS directives! -## Components +## Use with angular-spy -### cat-scroll-container and cat-visible directives -Detect when an element is fully visible (and fully hidden) in page, and sets -a scope variable accordingly +tl;dr - If you need to run animations on scroll, have a look at +[angular-spy](https://github.com/flea89/angular-spy) which works very well with +angular-cat. -```html - - [...] -
- When I'm visible, `runAnimation` is set to true -
- [...] - -``` -At the moment, these work only when there are no "nested" scrolling container. +Originally, angular-cat shipped with directives to handle scroll-based triggers. +We realized though, that coordinating CSS animation and handling scroll-spies +are two, quite different things, which can be very useful both by themselves or +used together. We decided then to split the two, keep responsibility separated, +footprint smaller and allow for more flexibility. -### cat-class directive +## Components -Play/stop CSS based animation using a `--start` class modifier. +### `cat-class` directive +Play/stop CSS based animation using a `--start` class modifier. +When the `cat-active` attribute becomes truthy the animation triggers, +by removing the `--start` suffix. ```html
Toggle status ``` -The fact that animations are defined "backward", makes it way easier to write CSS +The fact that animations are defined end-to-start, makes it way easier to write CSS for browser where you don't wont animations to run (mobile, legacy). The final status of the animation is defined by its default style (`.a-class-name`), while the `--start` version specifies how to set up the animation. -Note that we use `transition: none` to avoid running the transition "backward", when resetting it (but we could, if we wanted to!) - -### cat-delay directive +### `cat-delay` directive Introduce a delay in an animation sequence. This is particularly useful when you're sharing sub-animations and you need to fine tune delays on a per-case scenario. -### cat-timeline directive +### `cat-timeline` directive -Combine all its children directive and, (if `cat-active` is set) runs them in -sequence. +Combine all its children directive and, when `cat-active` is set, runs them in +sequence (default). This is where most of the magic happens. You can use the timeline to play a sequence of simple animations, nest them, or run them in whatever order you want! @@ -92,14 +87,14 @@ By default, the animations are run in sequence, and not repeated unless `cat-und
-
+
``` ## APIs -All directives can be controlled both declarative, via HTML attributes, or +All directives can be controlled both declaratively via HTML attributes, or programmatically, by requiring their angular controller. For most use cases, HTML attributes should be enough. You should care about the programmatic interface only if @@ -108,25 +103,41 @@ you're writing custom directives that needs to integrate with the timeline (or i ### Shared HTML interface #### `cat-active` -Used to trigger the animation when changes to true. It should be used as a "read only" scope variable by the animation directives and set from the outside (e.g. by `cat-visible`). +*Expression* + +Used to trigger the animation when the expression evaluates to true. It should be used as a *write-only* scope variable by the animation directives and set from the outside (e.g. by `spy-visible`). + +If you write a custom animation you should never set this variable. #### `cat-undo` +*expression* | Default: **false** -Tells the directive that the animation should be "cleared" (resetted) when `cat-active` switches back to `false` +Tells the directive that the animation should be "cleared" (resetted) when +`cat-active` switches back to `false`. This allow to replay the animations +more then once + +#### `cat-disabled` +*expression* | Default: **false** + +Specify that the animation should be disabled. This can be changed dynamically +to disable animations depending on your animation status #### `cat-animation-name` -Defaults to the directive name +*string* | Defaults to the directive name Name to be used when registering the animation on the parent `cat-timeline`. #### `cat-status` +*expression* | **readonly** + Read-only variable (written by the directives). Can be used to check the animation -status. Can be 'READY', 'RUNNING', 'CLEARING' or 'FINISHED'. Very useful if you want to -play animations only when other are finished, but don't or can't create a "global" animation for all of them. +status. It can assume the values of 'READY', 'RUNNING', 'SEEKING' or 'FINISHED'. +Very useful if you want to play animations only when other are finished, +but don't or can't create a "wrapper" animation for all of them. -### Additional HTML APIs +### Directive specific HTML APIs Not shared among all directives #### `cat-class=""` @@ -136,16 +147,30 @@ add a `--start` class to set-up the animation. #### `cat-delay=""` How long to wait, in milliseconds. - ### Shared JS interface +All APIs return a promise, which is resolved when the relative action is +successfully completed, or rejected otherwise. + ### `controller.play() : Promise` Start the animation and returns a promise, resolved when the animation is over. If called when the animation is already running, the same promise is returned. -### `controller.clear() : Promise` -Reset the animation to its initial state, returning a promise resolved when the -clearing operation is finished. +### `controller.seek(progress: string) : Promise` + +`progress` can be either the `'start'` or `'end'` string. + +Seek to the end or the beginning of the animation, based on the passed parameter. +If called when the animation is `RUNNING` it causes the play promise to be rejected. + +### `controller.setDisabled(disabled: boolean) : Promise` + +Set the disabled status of the animation. + +### `controller.setUp() : Promise` + +Called by the linking function on all animation directives. +You should probably never call this yourself. #### Additional JS APIs @@ -154,9 +179,14 @@ Called by "component" directives to register themselves. The order parameter is currently only available via JS APIs, but will be added as a shared attribute in the future. -### `timelineController.get()` //TODO -Not implemented yet, should allow to get a children animation from the controller -for custom animation ordering and sequencing (e.g. first two in parralle, than the third, than another two...) +### `timelineController.getAnimation(animationName: string) : animationController` + +Return a registered animation by name. + +### `timelineController.getAllAnimations(): object` + +Get all animations objects registered on the timeline controller. + [![Analytics](https://ga-beacon.appspot.com/UA-39387573-2/potato-animation/readme?pixel)](https://github.com/igrigorik/ga-beacon) From 28166e579029860bf577dd6220500a3c29fdd2e1 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Tue, 15 Mar 2016 23:12:29 +0100 Subject: [PATCH 05/11] Use correct heading #47 --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 733e8f7..2fce801 100644 --- a/README.md +++ b/README.md @@ -152,38 +152,38 @@ How long to wait, in milliseconds. All APIs return a promise, which is resolved when the relative action is successfully completed, or rejected otherwise. -### `controller.play() : Promise` +#### `controller.play() : Promise` Start the animation and returns a promise, resolved when the animation is over. If called when the animation is already running, the same promise is returned. -### `controller.seek(progress: string) : Promise` +#### `controller.seek(progress: string) : Promise` `progress` can be either the `'start'` or `'end'` string. Seek to the end or the beginning of the animation, based on the passed parameter. If called when the animation is `RUNNING` it causes the play promise to be rejected. -### `controller.setDisabled(disabled: boolean) : Promise` +#### `controller.setDisabled(disabled: boolean) : Promise` Set the disabled status of the animation. -### `controller.setUp() : Promise` +#### `controller.setUp() : Promise` Called by the linking function on all animation directives. You should probably never call this yourself. -#### Additional JS APIs +### Additional JS APIs -### `timelineController.register(animationName: string, animationController [, order: number])` +#### `timelineController.register(animationName: string, animationController [, order: number])` Called by "component" directives to register themselves. The order parameter is currently only available via JS APIs, but will be added as a shared attribute in the future. -### `timelineController.getAnimation(animationName: string) : animationController` +#### `timelineController.getAnimation(animationName: string) : animationController` Return a registered animation by name. -### `timelineController.getAllAnimations(): object` +#### `timelineController.getAllAnimations(): object` Get all animations objects registered on the timeline controller. From 3fd3f5706938a203a1036b1d4d8241151368d298 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Wed, 16 Mar 2016 00:07:32 +0100 Subject: [PATCH 06/11] Add book.json config file --- book.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 book.json diff --git a/book.json b/book.json new file mode 100644 index 0000000..8c83b00 --- /dev/null +++ b/book.json @@ -0,0 +1,8 @@ +{ + "plugins": ["github"], + "pluginsConfig": { + "github": { + "url": "https://github.com/artoale/angular-cat/" + } + } +} From 80a9aa8969a86466dc84ec76e905de10aa75b1f8 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Thu, 17 Mar 2016 01:05:18 +0100 Subject: [PATCH 07/11] Git book, first pages --- README.md | 117 ++++++++++++++++------------------------- book.json | 4 ++ docs/README.md | 5 ++ docs/api/Directives.md | 48 +++++++++++++++++ docs/api/README.md | 55 +++++++++++++++++++ 5 files changed, 157 insertions(+), 72 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/api/Directives.md create mode 100644 docs/api/README.md diff --git a/README.md b/README.md index 2fce801..b9e7b30 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,37 @@ # CAT - CSS Animation Timeline -[![Travis CI Build Status](https://travis-ci.org/artoale/angular-cat.svg)](https://travis-ci.org/artoale/angular-cat) -[![codecov.io](https://codecov.io/github/artoale/angular-cat/coverage.svg?branch=master)](https://codecov.io/github/artoale/angular-cat?branch=master) +[![Travis CI Build Status](https://travis-ci.org/artoale/angular-cat.svg)](https://travis-ci.org/artoale/angular-cat) [![codecov.io](https://codecov.io/github/artoale/angular-cat/coverage.svg?branch=master)](https://codecov.io/github/artoale/angular-cat?branch=master) -Set of AngularJS directives to simplify development and composition of -animations on static websites. +Set of AngularJS directives to simplify development and composition of animations on static websites. ## Why -Most modern "static" websites, have loads of complicated animations, often built -out of smaller bits. -You need to sequence CSS transition, CSS animations and custom javascript stuff, manage -scroll-based trigger, make those repeatable. -You don't wont to stick all this complexity in a controller which not only shouldn't be there just because of animations, -you'll also end up having code duplication and find yourself in a code maintenance hell. +Most modern "static" websites, have loads of complicated animations, often built out of smaller bits. You need to sequence CSS transition, CSS animations and custom javascript stuff, manage scroll-based trigger, make those repeatable. + +You don't wont to stick all this complexity in a controller which not only shouldn't be there just because of animations, you'll also end up having code duplication and find yourself in a code maintenance hell. ## The solution A set of -* Declarative -* Reusable -* Composable -* Nestable + +- Declarative +- Reusable +- Composable +- Nestable AngularJS directives! ## Use with angular-spy -tl;dr - If you need to run animations on scroll, have a look at -[angular-spy](https://github.com/flea89/angular-spy) which works very well with -angular-cat. +tl;dr - If you need to run animations on scroll, have a look at [angular-spy](https://github.com/flea89/angular-spy) which works very well with angular-cat. -Originally, angular-cat shipped with directives to handle scroll-based triggers. -We realized though, that coordinating CSS animation and handling scroll-spies -are two, quite different things, which can be very useful both by themselves or -used together. We decided then to split the two, keep responsibility separated, -footprint smaller and allow for more flexibility. +Originally, angular-cat shipped with directives to handle scroll-based triggers. We realized though, that coordinating CSS animation and handling scroll-spies are two, quite different things, which can be very useful both by themselves or used together. We decided then to split the two, keep responsibility separated, footprint smaller and allow for more flexibility. ## Components ### `cat-class` directive -Play/stop CSS based animation using a `--start` class modifier. -When the `cat-active` attribute becomes truthy the animation triggers, -by removing the `--start` suffix. +Play/stop CSS based animation using a `--start` class modifier. When the `cat-active` attribute becomes truthy the animation triggers, by removing the `--start` suffix. ```html + + +
I am a red text!
+ +``` + +### Additional HTML APIs + +#### `cat-class` attribute + +Specifies the class which defines the animation. Note that the directive automatically adds this class to the element `classList` so you won't need to add it unless you need to support browser with no javascript. + +## catDelay + +### Usage example + +Animations that just waits for the given time in milliseconds. + +This directive, practically useless on it's own, is very powerful and useful when dealing with complex animation since it allows to introduce delays declaratively. It allows to keep delays outside of CSS transitions and animations, making them much more reusable. + +```html +
+``` diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..0bf1a1b --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,55 @@ +# API Reference + +All the built-in directives share a common interface, accessible both via html attributes and via a javascript interface, which can be accessed by requiring the directive controller in your own custom directive. + +In this documentation we sometimes refer to attributes as **read-only** when we intend that only the directive should write these variable (not you!) and **write-only** when a variable is guaranteed to never be set by the directive, but only meant to be set by the outside (you!). + +## HTML attributes + +`cat-active` _expression_ | **write-only** + +Used to trigger the animation when the expression evaluates to true. It should be used as a _write-only_ scope variable by the animation directives and set from the outside (e.g. by `spy-visible`). + +If you write a custom animation you should never set this variable. + + `cat-undo` _expression_ | Default: `false` + +Tells the directive that the animation should be "cleared" (resetted) when `cat-active` switches back to `false`. This allow to replay the animations more then once + + `cat-disabled` _expression_ | Default: `false` + +Specify that the animation should be disabled. This can be changed dynamically to disable animations depending on your animation status + + `cat-animation-name` _string_ | Defaults to the directive name + +Name to be used when registering the animation on the parent `cat-timeline`. + + `cat-status` _expression_ | **read-only** + +Read-only variable (written by the directives). Can be used to check the animation status. It can assume the values of 'READY', 'RUNNING', 'SEEKING' or 'FINISHED'. + +Very useful if you want to play animations only when other are finished, but don't or can't create a "wrapper" animation for all of them. + +## Animation JS interface + +All APIs return a promise, which is resolved when the relative action is successfully completed, or rejected otherwise. + +To get an instance of the animation controller you can either require it directly in your own directives or use `timeline` specific functions if you are writing a custom timeline. + +`controller.play() : Promise` + +Start the animation and returns a promise, resolved when the animation is over. If called when the animation is already running, the same promise is returned. + +`controller.seek(progress: string) : Promise` + +Argument `progress` can be either the `'start'` or `'end'` string. + +Seek to the end or the beginning of the animation, based on the passed parameter. If called when the animation is `RUNNING` it causes the play promise to be rejected. + +`controller.setDisabled(disabled: boolean) : Promise` + +Set the disabled status of the animation. + +`controller.setUp() : Promise` + +Called by the linking function on all animation directives to notify the controller that the animation is ready. You should probably never need to call this yourself. From 75b499b8364633ceff843f7fe56be26c64445dc8 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Thu, 17 Mar 2016 01:08:12 +0100 Subject: [PATCH 08/11] Make link absolute in summary --- docs/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 22bcaac..c6d8e20 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,6 @@ # Table of contents -* [API Reference](./api/README.md) - * Directives - * Services +- [API Reference](/docs/api/README.md) + + - [Directives](/docs/api/Directives.md) + - Services From 128de8cb34a693b4269dd23cde5b1d1dd0aadb0d Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Fri, 18 Mar 2016 00:22:10 +0100 Subject: [PATCH 09/11] Add catTimeline docs --- docs/api/Directives.md | 50 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/docs/api/Directives.md b/docs/api/Directives.md index 038faeb..6dd1a12 100644 --- a/docs/api/Directives.md +++ b/docs/api/Directives.md @@ -1,5 +1,7 @@ # Directives documentation +Here you can find directives specific APIs and description. Bear in mind that all directives share a common HTML and JS API, described [here](./README.md). + ## catClass Trigger CSS animation playback. @@ -35,13 +37,57 @@ As you can see, animation are written inside-out to make it easy to support brow Specifies the class which defines the animation. Note that the directive automatically adds this class to the element `classList` so you won't need to add it unless you need to support browser with no javascript. -## catDelay +-------------------------------------------------------------------------------- + +## catTimeline + +Combine all its children directives and runs them in sequence. + +This is where most of the magic happens. All anglar-cat directives register themselves on the first parent `cat-timeline` (if any). The timeline supports basic sequence animation by default, but you can customize it easily. + +Please note that `cat-timeline` is an animation itself, meaning that you can nest them as much as you want, to build up complex sequences from simple ones. ### Usage example +```html +
+
+
+
+
+``` + +### Additional JS APIs + +#### `timelineController.register(animationName: string, animationController [, order: number])` + +Called by "component" directives to register themselves. The order parameter is currently only available via JS APIs, but we plan on adding it in a future release. + +#### `timelineController.getAnimation(animationName: string) : animationController` + +Return a registered animation by name. + +#### `timelineController.getAllAnimations(): object` + +Get all animations controller registered on the timeline controller. Returns an object hash with the animation name as key, the controller as value. + +#### `timelineController.setCustomAnimation(customRunAnimation: fn)` + +Specify a custom function for running the animation. + +The function is passed a promise to wait on and should return a promise resolved when the compound animation is finished. See the `cat-timeline` demo for an example of how to write custom animation timelines. + +-------------------------------------------------------------------------------- + +## catDelay + Animations that just waits for the given time in milliseconds. -This directive, practically useless on it's own, is very powerful and useful when dealing with complex animation since it allows to introduce delays declaratively. It allows to keep delays outside of CSS transitions and animations, making them much more reusable. +### Usage example + +This directive, practically useless on its own, is very powerful and useful when dealing with complex animation since it allows to introduce delays declaratively. It allows to keep delays outside of CSS transitions and animations, making them much more reusable. + +Delay specified in milliseconds. ```html
From 838854e7a04dd51bf8311ce2a6fa45203241b7c3 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Fri, 18 Mar 2016 00:46:11 +0100 Subject: [PATCH 10/11] Update README.md, remove API details and link to guide --- README.md | 167 ++++++++++++++---------------------------------------- 1 file changed, 43 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index b9e7b30..1015864 100644 --- a/README.md +++ b/README.md @@ -21,145 +21,64 @@ A set of AngularJS directives! -## Use with angular-spy - -tl;dr - If you need to run animations on scroll, have a look at [angular-spy](https://github.com/flea89/angular-spy) which works very well with angular-cat. - -Originally, angular-cat shipped with directives to handle scroll-based triggers. We realized though, that coordinating CSS animation and handling scroll-spies are two, quite different things, which can be very useful both by themselves or used together. We decided then to split the two, keep responsibility separated, footprint smaller and allow for more flexibility. - -## Components +## Installation -### `cat-class` directive +You can do the usual, with npm -Play/stop CSS based animation using a `--start` class modifier. When the `cat-active` attribute becomes truthy the animation triggers, by removing the `--start` suffix. - -```html - -
I am a red text!
- +``` +npm install angular-cat ``` -The fact that animations are defined end-to-start, makes it way easier to write CSS for browser where you don't wont animations to run (mobile, legacy). The final status of the animation is defined by its default style (`.a-class-name`), while the `--start` version specifies how to set up the animation. +or, with bower -### `cat-delay` directive +``` +bower install angular-cat +``` -Introduce a delay in an animation sequence. This is particularly useful when you're sharing sub-animations and you need to fine tune delays on a per-case scenario. +Both regular and minified files can be found in the `dist/` folder. -### `cat-timeline` directive +## Use with angular-spy -Combine all its children directive and, when `cat-active` is set, runs them in sequence (default). +tl;dr - If you need to run animations on scroll, have a look at [angular-spy](https://github.com/flea89/angular-spy) which works very well with angular-cat. -This is where most of the magic happens. You can use the timeline to play a sequence of simple animations, nest them, or run them in whatever order you want! +Originally, angular-cat shipped with directives to handle scroll-based triggers. We realized though, that coordinating CSS animation and handling scroll-spies are two, quite different things, which can be very useful both by themselves or used together. We decided then to split the two, keep responsibility separated, footprint smaller and allow for more flexibility. -By default, the animations are run in sequence, and not repeated unless `cat-undo` is specified. +## At a glance ```html -
-
+ +
+ +
I'll be red
-
+
I'll be wider
-``` - -## APIs - -All directives can be controlled both declaratively via HTML attributes, or programmatically, by requiring their angular controller. - -For most use cases, HTML attributes should be enough. You should care about the programmatic interface only if you're writing custom directives that needs to integrate with the timeline (or if you're contributing to this project!) - -### Shared HTML interface - -#### `cat-active` - -_Expression_ - -Used to trigger the animation when the expression evaluates to true. It should be used as a _write-only_ scope variable by the animation directives and set from the outside (e.g. by `spy-visible`). - -If you write a custom animation you should never set this variable. - -#### `cat-undo` - -_expression_ | Default: **false** - -Tells the directive that the animation should be "cleared" (resetted) when `cat-active` switches back to `false`. This allow to replay the animations more then once - -#### `cat-disabled` - -_expression_ | Default: **false** - -Specify that the animation should be disabled. This can be changed dynamically to disable animations depending on your animation status -#### `cat-animation-name` - -_string_ | Defaults to the directive name - -Name to be used when registering the animation on the parent `cat-timeline`. - -#### `cat-status` - -_expression_ | **readonly** - -Read-only variable (written by the directives). Can be used to check the animation status. It can assume the values of 'READY', 'RUNNING', 'SEEKING' or 'FINISHED'. - -Very useful if you want to play animations only when other are finished, but don't or can't create a "wrapper" animation for all of them. - -### Directive specific HTML APIs - -Not shared among all directives - -#### `cat-class=""` - -Tells the directive to use `` as CSS animation class: this will make the animation add a `--start` class to set-up the animation. - -#### `cat-delay=""` - -How long to wait, in milliseconds. - -### Shared JS interface - -All APIs return a promise, which is resolved when the relative action is successfully completed, or rejected otherwise. - -#### `controller.play() : Promise` - -Start the animation and returns a promise, resolved when the animation is over. If called when the animation is already running, the same promise is returned. - -#### `controller.seek(progress: string) : Promise` - -`progress` can be either the `'start'` or `'end'` string. - -Seek to the end or the beginning of the animation, based on the passed parameter. If called when the animation is `RUNNING` it causes the play promise to be rejected. - -#### `controller.setDisabled(disabled: boolean) : Promise` - -Set the disabled status of the animation. - -#### `controller.setUp() : Promise` - -Called by the linking function on all animation directives. You should probably never call this yourself. - -### Additional JS APIs - -#### `timelineController.register(animationName: string, animationController [, order: number])` - -Called by "component" directives to register themselves. The order parameter is currently only available via JS APIs, but will be added as a shared attribute in the future. - -#### `timelineController.getAnimation(animationName: string) : animationController` - -Return a registered animation by name. + +``` -#### `timelineController.getAllAnimations(): object` +As you can see, the basic usage is declarative, expressive and CSS friendly. -Get all animations objects registered on the timeline controller. +## Resources -[![Analytics](https://ga-beacon.appspot.com/UA-39387573-2/potato-animation/readme?pixel)](https://github.com/igrigorik/ga-beacon) +For more information and resources head to our [guide](https://artoale.gitbooks.io/angular-cat/content/index.html) or have a look at the [examples](https://github.com/artoale/angular-cat/tree/master/example). From 6a277cccdb6eb98717c8ecdbaa98d71a5e7e1925 Mon Sep 17 00:00:00 2001 From: Alessandro Artoni Date: Wed, 23 Mar 2016 00:01:48 +0100 Subject: [PATCH 11/11] Add services documentation --- docs/README.md | 2 +- docs/api/Services.md | 71 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 docs/api/Services.md diff --git a/docs/README.md b/docs/README.md index c6d8e20..47ea77a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,4 +3,4 @@ - [API Reference](/docs/api/README.md) - [Directives](/docs/api/Directives.md) - - Services + - [Services](/docs/api/Services.md) diff --git a/docs/api/Services.md b/docs/api/Services.md new file mode 100644 index 0000000..6bcfd6e --- /dev/null +++ b/docs/api/Services.md @@ -0,0 +1,71 @@ +# Services documentation + +This document describes the injectable services provided by angular-cat. + +These services are mostly for usage when implementing custom animations. + +## catDelayS + +`catDelayS(millis: number): Promise` + +Utility function that returns a `$q` promise resolved after `millis` milliseconds, similar to `Q.delay` + +## catBaseAnimation + +Used internally by all our directives controller. + +It handles managing the internal state of the animation and greatly reduce the complexity of writing custom animations. It returns all the function defined by the shared js API and allow each of those to be customized. + +When creating an instance of the base animation, make sure to pass along the $scope and $attrs injectable. + +### Usage example + +Inside a directive definition: + +```javascript +// ... +controller: function DirectiveController($scope, $attrs) { + let baseAnimation = catBaseAnimation({ + // Define one or more custom handlers + onSetUp: function () {...}, + onPlay: function() {...} + // ... + $scope: $scope, + $attrs: $attrs + }); + + // Copy all function so that those are exposed on our controller + angular.extend(this, baseAnimation); + + // same as + // this.setUp = baseAnimation.setUp; + // this.seek = baseAnimation.seek; + // this.play = baseAnimation.play; + // this.setDisabled = baseAnimation.setDisabled; +} +// ... +``` + +`catBaseAnimation(config: object) : animationApi` + +The `config` object allows to customize the behavior of the animation. All its properties should be function optionally returning a promise, resolved when the respective action is completed. All properties are optional. + +`config.onSetup()` + +Called during directive linking phase. All other API call wait on this promise before executing - which means you can use it to do any synchronous or asynchronous setup task (e.g. you could preload a video to make sure it's ready when running an animation). + +`config.onPlay()` + +Called to actually play the animation. This is where most of the magic happens. It's called by baseAnimation to trigger animation playback and any custom animation implementation should implement this function and make sure to return a promise resolved when the animation is finished. + +`config.onSeek(progress: string)` + +Called when seeking to either the end or beginning of an animation. `progress` can be either the string `'end'` or `'start'`. Be aware that seek is called by base animation in many different scenarios depending on the current animation state - and thus it should have immediate visual result (e.g. in a CSS based transition, `element.style.transition` should be set to `'none'` to avoid visual glitches) + +`config.disable()` + +This is called to notify that an animation status will change to `DISABLED`. Most of the time, you don't need to implement this, unless you need some visual effect that shows the animation is running. + +## catAnimationLink + +This function should be called with all your linking function arguments inside it. It takes care of watching the correct _shared_ html attributes, registering on (if any) parent timeline controller and calling the controller `setUp` function.