diff --git a/src/gallery-anim-native/build.json b/src/gallery-anim-native/build.json new file mode 100644 index 0000000000..fc5e074a20 --- /dev/null +++ b/src/gallery-anim-native/build.json @@ -0,0 +1,16 @@ +{ + "name": "gallery-anim-native", + "builds": { + "gallery-anim-native": { + "jsfiles": [ + "anim-native.js" + ], + "config": { + "use": [ + "node", + "base" + ] + } + } + } +} diff --git a/src/gallery-anim-native/build.properties b/src/gallery-anim-native/build.properties new file mode 100644 index 0000000000..46a2f3f873 --- /dev/null +++ b/src/gallery-anim-native/build.properties @@ -0,0 +1,8 @@ +# Anim Native Build Properties +builddir=../../../builder/componentbuild +component=gallery-anim-native +component.jsfiles=anim-native.js +component.requires= +component.supersedes= +component.optional= +component.skinnable=false diff --git a/src/gallery-anim-native/build.xml b/src/gallery-anim-native/build.xml new file mode 100644 index 0000000000..c1ff6a0130 --- /dev/null +++ b/src/gallery-anim-native/build.xml @@ -0,0 +1,7 @@ + + + + Anim Native Build File + + + \ No newline at end of file diff --git a/src/gallery-anim-native/js/anim-native.js b/src/gallery-anim-native/js/anim-native.js new file mode 100644 index 0000000000..2b9c01c0c8 --- /dev/null +++ b/src/gallery-anim-native/js/anim-native.js @@ -0,0 +1,550 @@ +/** +* The Animation Utility provides an API for creating advanced transitions. +* +* W3C CSS Animations: +* http://www.w3.org/TR/css3-animations/ +* +* Easing method values from AliceJS: +* http://blackberry.github.com/Alice/ +* +* Browser support: +* http://caniuse.com/#feat=css-animation +* IE10+, FF5+, Chrome 4+, Safari/iOS 4+, Android 2.1+ +* +* @module anim-native +*/ + +/** +* Provides the CSS3 Native Anim class, for animating CSS properties. +* +* @module anim +* @submodule anim-native +*/ + "use strict"; + /*global Y:true */ + /*jslint regexp: true*/ + var VENDOR = ['', 'webkit', 'Moz', 'O', 'ms'].filter(function (prefix) { + return Y.config.doc.body.style.hasOwnProperty(prefix + 'Animation'); + })[0], + PREFIX = VENDOR ? '-' + VENDOR.toLowerCase() + '-' : VENDOR, + ANIMATION_END_VENDORS = { + webkit: 'webkitAnimationEnd', + O: 'oAnimationEnd' + }, + ANIMATION_END_EVENT = 'animationend', + ANIMATION_END = ANIMATION_END_VENDORS[VENDOR] || ANIMATION_END_EVENT, + + /** + * A class for constructing animation instances. + * @class Anim + * @for Anim + * @constructor + * @extends Base + */ + Anim = function () { + Anim.superclass.constructor.apply(this, arguments); + }; + + Y.Node.DOM_EVENTS[ANIMATION_END] = 1; + + Anim.NAME = 'animNative'; + Anim.DIRECTIONS = { + normal: ['normal', 'reverse'], + alternate: ['alternate', 'alternate-reverse'] + }; + Anim.EASINGS = { + easeNone: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + easeIn: {p1: 0.420, p2: 0.000, p3: 1.000, p4: 1.000}, + easeOut: {p1: 0.000, p2: 0.000, p3: 0.580, p4: 1.000}, + easeBoth: {p1: 0.420, p2: 0.000, p3: 0.580, p4: 1.000}, + easeInStrong: {p1: 0.895, p2: 0.030, p3: 0.685, p4: 0.220}, + easeOutStrong: {p1: 0.165, p2: 0.840, p3: 0.440, p4: 1.000}, + easeBothStrong: {p1: 0.770, p2: 0.000, p3: 0.175, p4: 1.000}, + backOut: {p1: 0.175, p2: 0.885, p3: 0.320, p4: 1.275}, + backBoth: {p1: 0.680, p2: -0.550, p3: 0.265, p4: 1.550}, + + // FIXME: Defaulting these to linear + elasticIn: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + elasticOut: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + elasticBoth: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + backIn: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + bounceIn: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + bounceOut: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750}, + bounceBoth: {p1: 0.250, p2: 0.250, p3: 0.750, p4: 0.750} + }; + + Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/; + + /** + * Regex of properties that should use the default unit. + * + * @property RE_DEFAULT_UNIT + * @static + */ + Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i; + + /** + * The default unit to use with properties that pass the RE_DEFAULT_UNIT test. + * + * @property DEFAULT_UNIT + * @static + */ + Anim.DEFAULT_UNIT = 'px'; + + Anim._easing = function (name) { + var e = Anim.EASINGS[name]; + return 'cubic-bezier(' + e.p1 + ', ' + e.p2 + ', ' + e.p3 + ', ' + e.p4 + ')'; + }; + + Anim._toHyphen = function (property) { + property = property.replace(/([A-Z]?)([a-z]+)([A-Z]?)/g, function (m0, m1, m2, m3) { + var str = ((m1) ? '-' + m1.toLowerCase() : '') + m2; + + if (m3) { + str += '-' + m3.toLowerCase(); + } + + return str; + }); + + return property; + }; + + Anim._insert = function (rule) { + Y.log(rule); + var doc = Y.config.doc, + ruleNum, + style; + + if (doc.styleSheets && doc.styleSheets.length) { + ruleNum = 0; + try { + if (doc.styleSheets[0].cssRules.length > 0) { + ruleNum = doc.styleSheets[0].cssRules.length; + } + doc.styleSheets[0].insertRule(rule, ruleNum); + } catch (e) { + Y.log(e.message + rule, 'warn'); + } + } else { + style = doc.createElement("style"); + style.innerHTML = rule; + doc.head.appendChild(style); + } + }; + + Anim._delete = function (ruleName) { + var doc = Y.config.doc, + cssrules = doc.all ? "rules" : "cssRules", + i; + + for (i = 0; i < doc.styleSheets[0][cssrules].length; i += 1) { + if (doc.styleSheets[0][cssrules][i].name === ruleName) { + doc.styleSheets[0].deleteRule(i); + break; + } + } + }; + + Anim.ATTRS = { + /** + * The object to be animated. + * @attribute node + * @type Node + */ + node: { + setter: function (node) { + if (node) { + if (typeof node === 'string' || node.nodeType) { + node = Y.one(node); + } + } + + this._node = node; + if (!node) { + Y.log(node + ' is not a valid node', 'warn', 'Anim'); + } + return node; + } + }, + + /** + * The length of the animation. Defaults to "1" (second). + * @attribute duration + * @type NUM + */ + duration: { + value: 1 + }, + + /** + * The method that will provide values to the attribute(s) during the animation. + * Defaults to "easeNone". + * @attribute easing + * @type Function + */ + easing: { + value: 'easeNone', + setter: function (e) { + return Anim._easing(e); + } + }, + + /** + * The starting values for the animated properties. + * + * Fields may be strings, numbers, or functions. + * If a function is used, the return value becomes the from value. + * If no from value is specified, the DEFAULT_GETTER will be used. + * Supports any unit, provided it matches the "to" (or default) + * unit (e.g. `{width: '10em', color: 'rgb(0, 0, 0)', borderColor: '#ccc'}`). + * + * If using the default ('px' for length-based units), the unit may be omitted + * (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels + * and hex, respectively). + * + * @attribute from + * @type Object + */ + from: { + value: {} + }, + + /** + * The keyframes between 0 and 100%. + * + * Example: {'50%': { + * width: 200 + * }} + * + * @attribute to + * @type Object + */ + frames: { + value: {} + }, + + /** + * The ending values for the animated properties. + * + * Fields may be strings, numbers, or functions. + * Supports any unit, provided it matches the "from" (or default) + * unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`). + * + * If using the default ('px' for length-based units), the unit may be omitted + * (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels + * and hex, respectively). + * + * @attribute to + * @type Object + */ + to: { + value: {} + }, + + /** + * Date stamp for the first frame of the animation. + * @attribute startTime + * @type Int + * @default 0 + * @readOnly + */ + startTime: { + value: 0, + readOnly: true + }, + + /** + * Current time the animation has been running. + * @attribute elapsedTime + * @type Int + * @default 0 + * @readOnly + */ + elapsedTime: { + value: 0, + readOnly: true + }, + + /** + * Whether or not the animation is currently running. + * @attribute running + * @type Boolean + * @default false + * @readOnly + */ + running: { + getter: function () { + return this.get('node').getStyle(VENDOR + "AnimationName") !== 'none'; + }, + value: false, + readOnly: true + }, + + /** + * The number of seconds to delay the animation + * @attribute delay + * @type Int + * @default 0 + */ + delay: { + value: 0 + }, + + /** + * The number of times the animation should run. 'infinite' or integer. + * @attribute iterations + * @type Int + * @default 1 + */ + iterations: { + value: 1 + }, + + /** + * The number of iterations that have occurred. + * Resets when an animation ends (reaches iteration count or stop() called). + * + * Note: no way to update this mid animation. + * + * @attribute iterationCount + * @type Int + * @default 0 + * @readOnly + */ + iterationCount: { + value: 0, + readOnly: true + }, + + /** + * How iterations of the animation should behave. + * Possible values are "normal" and "alternate". + * Normal will repeat the animation, alternate will reverse on every other pass. + * + * @attribute direction + * @type String + * @default "normal" + */ + direction: { + value: 'normal' // | alternate (fwd on odd, rev on even per spec) + }, + + /** + * Whether or not the animation is currently paused. + * @attribute paused + * @type Boolean + * @default false + * @readOnly + */ + paused: { + getter: function () { + return this.get('node').getStyle(VENDOR + "AnimationPlayState") === 'paused'; + }, + readOnly: true, + value: false + }, + + /** + * If true, animation begins from last frame + * @attribute reverse + * @type Boolean + * @default false + */ + reverse: { + value: false + }, + + /** + * Perspective depth + * @attribute perspective + * @type int + * @default 1000 + */ + perspective: { + value: 1000 + }, + + /** + * X/Y axis origin + * @attribute perspectiveOrigin + * @type String + * @default 'center center' + */ + perspectiveOrigin: { + value: 'center center' + }, + + /** + * If 'visible' the element is show when not facing the screen. If 'hidden' the + * element will be invisible when not facing the screen. + * @attribute backfaceVisibility + * @type String + * @default 'visible' + */ + backfaceVisibility: { + value: 'visible' + } + }; + + Y.extend(Anim, Y.Base, { + initializer: function (config) {}, + + /** + * Starts or resumes an animation. + * @method run + * @chainable + */ + run: function () { + if (this.get('paused')) { + this._resume(); + } else if (!this.get('running')) { + this._start(); + } + return this; + }, + + /** + * Pauses the animation and + * freezes it in its current state and time. + * Calling run() will continue where it left off. + * @method pause + * @chainable + */ + pause: function () { + if (this.get('running')) { + this.get('node').setStyle(VENDOR + "AnimationPlayState", 'paused'); + this.fire('pause'); + } + return this; + }, + + /** + * Stops the animation and resets its time. + * @method stop + * @param {Boolean} finish If true, the animation will move to the last frame + * @chainable + */ + stop: function (finish) { + this._end(); + return this; + }, + + _resume: function () { + this.get('node').setStyle(VENDOR + "AnimationPlayState", 'running'); + this.fire('resume'); + }, + + /** + * Initializes animation. Inserts keyframes into DOM and updates node styles. + */ + _start: function () { + var node = this.get('node'), + parent = node.get('parentNode'), + name = 'anim-' + Y.guid(), + direction = Anim.DIRECTIONS[this.get('direction')][+this.get('reverse')], + from = this.get('from'), + to = this.get('to'), + frames = this.get('frames'), + keyframes = {}, + res, + frame; + + keyframes['0%'] = from; + keyframes = Y.merge(keyframes, frames); + keyframes['100%'] = to; + + res = this._render(node, name, keyframes); + + // Apply last animation frame styles + if (this.get('iterations') !== 'infinite') { + frame = this.get('iterations') % (2 - this.get('reverse')) * 100; + node.setStyles(res.styles[frame + '%']); + } + + Anim._insert(res.css); + + this.set('iterationCount', 0); + + parent.setStyle(VENDOR + "Perspective", this.get('perspective') + "px"); + parent.setStyle(VENDOR + "PerspectiveOrigin", this.get('perspectiveOrigin')); + node.setStyle(VENDOR + "AnimationDuration", this.get('duration') + 's'); + node.setStyle(VENDOR + "AnimationTimingFunction", this.get('easing')); + node.setStyle(VENDOR + "AnimationDelay", this.get('delay') + 's'); + node.setStyle(VENDOR + "AnimationIterationCount", this.get('iterations')); + node.setStyle(VENDOR + "AnimationDirection", direction); + node.setStyle(VENDOR + "AnimationPlayState", 'running'); + node.setStyle(VENDOR + "BackfaceVisibility", this.get('backfaceVisibility')); + node.setStyle(VENDOR + "AnimationName", name); + + this.fire('start'); + this._sub = node.on(ANIMATION_END, this._end, this); + }, + + _end: function () { + var node = this.get('node'), + name = node.getStyle(VENDOR + "AnimationName"); + + node.detach(ANIMATION_END, this._end); + + node.setStyle(VENDOR + "AnimationName", "none"); + node.setStyle(VENDOR + "AnimationDuration", "0s"); + node.setStyle(VENDOR + "AnimationTimingFunction", "ease"); + node.setStyle(VENDOR + "AnimationDelay", "0s"); + node.setStyle(VENDOR + "AnimationIterationCount", "1"); + node.setStyle(VENDOR + "AnimationDirection", "normal"); + node.setStyle(VENDOR + "AnimationPlayState", "running"); + node.setStyle(VENDOR + "BackfaceVisibility", 'visible'); + + // TODO: Restore parent node perspective? + + this.set('iterationCount', this.get('iterations')); + + Anim._delete(name); + this.fire('end', {elapsed: this.get('elapsedTime')}); + }, + + /** + * Generate CSS keyframes string and meta data. + */ + _render: function (node, name, keyframes) { + var css = ['@' + PREFIX + 'keyframes ' + name + ' {'], + styles = {}; + + Y.Object.each(keyframes, function (props, key) { + css.push('\t' + key + ' {'); + styles[key] = {}; + + Y.Object.each(props, function (value, prop) { + var parsed; + + if (typeof value === 'function') { + value = value.call(this, node); + } + + if (Anim.RE_DEFAULT_UNIT.test(prop)) { + parsed = Anim.RE_UNITS.exec(value); + + if (parsed && !parsed[2]) { + value += Anim.DEFAULT_UNIT; + } + } + + styles[key][prop] = value; + css.push('\t\t' + Anim._toHyphen(prop) + ': ' + value + ';'); + }, this); + + css.push('\t}'); + }, this); + + css.push('}'); + return {css: css.join('\n'), styles: styles}; + }, + + destructor: function () { + this.get('node').detach(ANIMATION_END, this._end); + } + }); + + Y.Anim = Anim; + Y.AnimNative = Anim; \ No newline at end of file diff --git a/src/gallery-anim-native/tests/manual/index.html b/src/gallery-anim-native/tests/manual/index.html new file mode 100644 index 0000000000..0b93715c63 --- /dev/null +++ b/src/gallery-anim-native/tests/manual/index.html @@ -0,0 +1,91 @@ + + + + + Anim Native + + + + +
+ + + + + + + + \ No newline at end of file