Path: blob/trunk/third_party/closure/goog/fx/dom.js
2868 views
// Copyright 2005 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Predefined DHTML animations such as slide, resize and fade.16*17* @see ../demos/effects.html18*/1920goog.provide('goog.fx.dom');21goog.provide('goog.fx.dom.BgColorTransform');22goog.provide('goog.fx.dom.ColorTransform');23goog.provide('goog.fx.dom.Fade');24goog.provide('goog.fx.dom.FadeIn');25goog.provide('goog.fx.dom.FadeInAndShow');26goog.provide('goog.fx.dom.FadeOut');27goog.provide('goog.fx.dom.FadeOutAndHide');28goog.provide('goog.fx.dom.PredefinedEffect');29goog.provide('goog.fx.dom.Resize');30goog.provide('goog.fx.dom.ResizeHeight');31goog.provide('goog.fx.dom.ResizeWidth');32goog.provide('goog.fx.dom.Scroll');33goog.provide('goog.fx.dom.Slide');34goog.provide('goog.fx.dom.SlideFrom');35goog.provide('goog.fx.dom.Swipe');3637goog.require('goog.color');38goog.require('goog.events');39goog.require('goog.fx.Animation');40goog.require('goog.fx.Transition');41goog.require('goog.style');42goog.require('goog.style.bidi');4344goog.forwardDeclare('goog.events.EventHandler');45464748/**49* Abstract class that provides reusable functionality for predefined animations50* that manipulate a single DOM element51*52* @param {Element} element Dom Node to be used in the animation.53* @param {Array<number>} start Array for start coordinates.54* @param {Array<number>} end Array for end coordinates.55* @param {number} time Length of animation in milliseconds.56* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.57* @extends {goog.fx.Animation}58* @constructor59* @struct60*/61goog.fx.dom.PredefinedEffect = function(element, start, end, time, opt_acc) {62goog.fx.Animation.call(this, start, end, time, opt_acc);6364/**65* DOM Node that will be used in the animation66* @type {Element}67*/68this.element = element;6970/**71* Whether the element is rendered right-to-left. We cache this here for72* efficiency.73* @private {boolean|undefined}74*/75this.rightToLeft_;76};77goog.inherits(goog.fx.dom.PredefinedEffect, goog.fx.Animation);787980/**81* Called to update the style of the element.82* @protected83*/84goog.fx.dom.PredefinedEffect.prototype.updateStyle = goog.nullFunction;858687/**88* Whether the DOM element being manipulated is rendered right-to-left.89* @return {boolean} True if the DOM element is rendered right-to-left, false90* otherwise.91*/92goog.fx.dom.PredefinedEffect.prototype.isRightToLeft = function() {93if (!goog.isDef(this.rightToLeft_)) {94this.rightToLeft_ = goog.style.isRightToLeft(this.element);95}96return this.rightToLeft_;97};9899100/** @override */101goog.fx.dom.PredefinedEffect.prototype.onAnimate = function() {102this.updateStyle();103goog.fx.dom.PredefinedEffect.superClass_.onAnimate.call(this);104};105106107/** @override */108goog.fx.dom.PredefinedEffect.prototype.onEnd = function() {109this.updateStyle();110goog.fx.dom.PredefinedEffect.superClass_.onEnd.call(this);111};112113114/** @override */115goog.fx.dom.PredefinedEffect.prototype.onBegin = function() {116this.updateStyle();117goog.fx.dom.PredefinedEffect.superClass_.onBegin.call(this);118};119120121122/**123* Creates an animation object that will slide an element from A to B. (This124* in effect automatically sets up the onanimate event for an Animation object)125*126* Start and End should be 2 dimensional arrays127*128* @param {Element} element Dom Node to be used in the animation.129* @param {Array<number>} start 2D array for start coordinates (X, Y).130* @param {Array<number>} end 2D array for end coordinates (X, Y).131* @param {number} time Length of animation in milliseconds.132* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.133* @extends {goog.fx.dom.PredefinedEffect}134* @constructor135* @struct136*/137goog.fx.dom.Slide = function(element, start, end, time, opt_acc) {138if (start.length != 2 || end.length != 2) {139throw Error('Start and end points must be 2D');140}141goog.fx.dom.PredefinedEffect.apply(this, arguments);142};143goog.inherits(goog.fx.dom.Slide, goog.fx.dom.PredefinedEffect);144145146/** @override */147goog.fx.dom.Slide.prototype.updateStyle = function() {148var pos = (this.isRightPositioningForRtlEnabled() && this.isRightToLeft()) ?149'right' :150'left';151this.element.style[pos] = Math.round(this.coords[0]) + 'px';152this.element.style.top = Math.round(this.coords[1]) + 'px';153};154155156157/**158* Slides an element from its current position.159*160* @param {Element} element DOM node to be used in the animation.161* @param {Array<number>} end 2D array for end coordinates (X, Y).162* @param {number} time Length of animation in milliseconds.163* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.164* @extends {goog.fx.dom.Slide}165* @constructor166* @struct167*/168goog.fx.dom.SlideFrom = function(element, end, time, opt_acc) {169/** @type {?Array<number>} */170this.startPoint;171172var offsetLeft = this.isRightPositioningForRtlEnabled() ?173goog.style.bidi.getOffsetStart(element) :174/** @type {!HTMLElement} */ (element).offsetLeft;175var start = [offsetLeft, /** @type {!HTMLElement} */ (element).offsetTop];176goog.fx.dom.Slide.call(this, element, start, end, time, opt_acc);177};178goog.inherits(goog.fx.dom.SlideFrom, goog.fx.dom.Slide);179180181/** @override */182goog.fx.dom.SlideFrom.prototype.onBegin = function() {183var offsetLeft = this.isRightPositioningForRtlEnabled() ?184goog.style.bidi.getOffsetStart(this.element) :185this.element.offsetLeft;186this.startPoint = [187offsetLeft,188/** @type {!HTMLElement} */ (this.element).offsetTop189];190goog.fx.dom.SlideFrom.superClass_.onBegin.call(this);191};192193194195/**196* Creates an animation object that will slide an element into its final size.197* Requires that the element is absolutely positioned.198*199* @param {Element} element Dom Node to be used in the animation.200* @param {Array<number>} start 2D array for start size (W, H).201* @param {Array<number>} end 2D array for end size (W, H).202* @param {number} time Length of animation in milliseconds.203* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.204* @extends {goog.fx.dom.PredefinedEffect}205* @constructor206* @struct207*/208goog.fx.dom.Swipe = function(element, start, end, time, opt_acc) {209if (start.length != 2 || end.length != 2) {210throw Error('Start and end points must be 2D');211}212goog.fx.dom.PredefinedEffect.apply(this, arguments);213214/**215* Maximum width for element.216* @type {number}217* @private218*/219this.maxWidth_ = Math.max(this.endPoint[0], this.startPoint[0]);220221/**222* Maximum height for element.223* @type {number}224* @private225*/226this.maxHeight_ = Math.max(this.endPoint[1], this.startPoint[1]);227};228goog.inherits(goog.fx.dom.Swipe, goog.fx.dom.PredefinedEffect);229230231/**232* Animation event handler that will resize an element by setting its width,233* height and clipping.234* @protected235* @override236*/237goog.fx.dom.Swipe.prototype.updateStyle = function() {238var x = this.coords[0];239var y = this.coords[1];240this.clip_(Math.round(x), Math.round(y), this.maxWidth_, this.maxHeight_);241this.element.style.width = Math.round(x) + 'px';242var marginX =243(this.isRightPositioningForRtlEnabled() && this.isRightToLeft()) ?244'marginRight' :245'marginLeft';246247this.element.style[marginX] = Math.round(x) - this.maxWidth_ + 'px';248this.element.style.marginTop = Math.round(y) - this.maxHeight_ + 'px';249};250251252/**253* Helper function for setting element clipping.254* @param {number} x Current element width.255* @param {number} y Current element height.256* @param {number} w Maximum element width.257* @param {number} h Maximum element height.258* @private259*/260goog.fx.dom.Swipe.prototype.clip_ = function(x, y, w, h) {261this.element.style.clip =262'rect(' + (h - y) + 'px ' + w + 'px ' + h + 'px ' + (w - x) + 'px)';263};264265266267/**268* Creates an animation object that will scroll an element from A to B.269*270* Start and End should be 2 dimensional arrays271*272* @param {Element} element Dom Node to be used in the animation.273* @param {Array<number>} start 2D array for start scroll left and top.274* @param {Array<number>} end 2D array for end scroll left and top.275* @param {number} time Length of animation in milliseconds.276* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.277* @extends {goog.fx.dom.PredefinedEffect}278* @constructor279* @struct280*/281goog.fx.dom.Scroll = function(element, start, end, time, opt_acc) {282if (start.length != 2 || end.length != 2) {283throw Error('Start and end points must be 2D');284}285goog.fx.dom.PredefinedEffect.apply(this, arguments);286};287goog.inherits(goog.fx.dom.Scroll, goog.fx.dom.PredefinedEffect);288289290/**291* Animation event handler that will set the scroll position of an element.292* @protected293* @override294*/295goog.fx.dom.Scroll.prototype.updateStyle = function() {296if (this.isRightPositioningForRtlEnabled()) {297goog.style.bidi.setScrollOffset(this.element, Math.round(this.coords[0]));298} else {299this.element.scrollLeft = Math.round(this.coords[0]);300}301this.element.scrollTop = Math.round(this.coords[1]);302};303304305306/**307* Creates an animation object that will resize an element between two widths308* and heights.309*310* Start and End should be 2 dimensional arrays311*312* @param {Element} element Dom Node to be used in the animation.313* @param {Array<number>} start 2D array for start width and height.314* @param {Array<number>} end 2D array for end width and height.315* @param {number} time Length of animation in milliseconds.316* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.317* @extends {goog.fx.dom.PredefinedEffect}318* @constructor319* @struct320*/321goog.fx.dom.Resize = function(element, start, end, time, opt_acc) {322if (start.length != 2 || end.length != 2) {323throw Error('Start and end points must be 2D');324}325goog.fx.dom.PredefinedEffect.apply(this, arguments);326};327goog.inherits(goog.fx.dom.Resize, goog.fx.dom.PredefinedEffect);328329330/**331* Animation event handler that will resize an element by setting its width and332* height.333* @protected334* @override335*/336goog.fx.dom.Resize.prototype.updateStyle = function() {337this.element.style.width = Math.round(this.coords[0]) + 'px';338this.element.style.height = Math.round(this.coords[1]) + 'px';339};340341342343/**344* Creates an animation object that will resize an element between two widths345*346* Start and End should be numbers347*348* @param {Element} element Dom Node to be used in the animation.349* @param {number} start Start width.350* @param {number} end End width.351* @param {number} time Length of animation in milliseconds.352* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.353* @extends {goog.fx.dom.PredefinedEffect}354* @constructor355* @struct356*/357goog.fx.dom.ResizeWidth = function(element, start, end, time, opt_acc) {358goog.fx.dom.PredefinedEffect.call(359this, element, [start], [end], time, opt_acc);360};361goog.inherits(goog.fx.dom.ResizeWidth, goog.fx.dom.PredefinedEffect);362363364/**365* Animation event handler that will resize an element by setting its width.366* @protected367* @override368*/369goog.fx.dom.ResizeWidth.prototype.updateStyle = function() {370this.element.style.width = Math.round(this.coords[0]) + 'px';371};372373374375/**376* Creates an animation object that will resize an element between two heights377*378* Start and End should be numbers379*380* @param {Element} element Dom Node to be used in the animation.381* @param {number} start Start height.382* @param {number} end End height.383* @param {number} time Length of animation in milliseconds.384* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.385* @extends {goog.fx.dom.PredefinedEffect}386* @constructor387* @struct388*/389goog.fx.dom.ResizeHeight = function(element, start, end, time, opt_acc) {390goog.fx.dom.PredefinedEffect.call(391this, element, [start], [end], time, opt_acc);392};393goog.inherits(goog.fx.dom.ResizeHeight, goog.fx.dom.PredefinedEffect);394395396/**397* Animation event handler that will resize an element by setting its height.398* @protected399* @override400*/401goog.fx.dom.ResizeHeight.prototype.updateStyle = function() {402this.element.style.height = Math.round(this.coords[0]) + 'px';403};404405406407/**408* Creates an animation object that fades the opacity of an element between two409* limits.410*411* Start and End should be floats between 0 and 1412*413* @param {Element} element Dom Node to be used in the animation.414* @param {Array<number>|number} start 1D Array or Number with start opacity.415* @param {Array<number>|number} end 1D Array or Number for end opacity.416* @param {number} time Length of animation in milliseconds.417* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.418* @extends {goog.fx.dom.PredefinedEffect}419* @constructor420* @struct421*/422goog.fx.dom.Fade = function(element, start, end, time, opt_acc) {423if (goog.isNumber(start)) start = [start];424if (goog.isNumber(end)) end = [end];425426goog.fx.dom.Fade.base(427this, 'constructor', element, start, end, time, opt_acc);428429if (start.length != 1 || end.length != 1) {430throw Error('Start and end points must be 1D');431}432433/**434* The last opacity we set, or -1 for not set.435* @private {number}436*/437this.lastOpacityUpdate_ = goog.fx.dom.Fade.OPACITY_UNSET_;438};439goog.inherits(goog.fx.dom.Fade, goog.fx.dom.PredefinedEffect);440441442/**443* The quantization of opacity values to use.444* @private {number}445*/446goog.fx.dom.Fade.TOLERANCE_ = 1.0 / 0x400; // 10-bit color447448449/**450* Value indicating that the opacity must be set on next update.451* @private {number}452*/453goog.fx.dom.Fade.OPACITY_UNSET_ = -1;454455456/**457* Animation event handler that will set the opacity of an element.458* @protected459* @override460*/461goog.fx.dom.Fade.prototype.updateStyle = function() {462var opacity = this.coords[0];463var delta = Math.abs(opacity - this.lastOpacityUpdate_);464// In order to keep eager browsers from over-rendering, only update465// on a potentially visible change in opacity.466if (delta >= goog.fx.dom.Fade.TOLERANCE_) {467goog.style.setOpacity(this.element, opacity);468this.lastOpacityUpdate_ = opacity;469}470};471472473/** @override */474goog.fx.dom.Fade.prototype.onBegin = function() {475this.lastOpacityUpdate_ = goog.fx.dom.Fade.OPACITY_UNSET_;476goog.fx.dom.Fade.base(this, 'onBegin');477};478479480/** @override */481goog.fx.dom.Fade.prototype.onEnd = function() {482this.lastOpacityUpdate_ = goog.fx.dom.Fade.OPACITY_UNSET_;483goog.fx.dom.Fade.base(this, 'onEnd');484};485486487/**488* Animation event handler that will show the element.489*/490goog.fx.dom.Fade.prototype.show = function() {491this.element.style.display = '';492};493494495/**496* Animation event handler that will hide the element497*/498goog.fx.dom.Fade.prototype.hide = function() {499this.element.style.display = 'none';500};501502503504/**505* Fades an element out from full opacity to completely transparent.506*507* @param {Element} element Dom Node to be used in the animation.508* @param {number} time Length of animation in milliseconds.509* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.510* @extends {goog.fx.dom.Fade}511* @constructor512* @struct513*/514goog.fx.dom.FadeOut = function(element, time, opt_acc) {515goog.fx.dom.Fade.call(this, element, 1, 0, time, opt_acc);516};517goog.inherits(goog.fx.dom.FadeOut, goog.fx.dom.Fade);518519520521/**522* Fades an element in from completely transparent to fully opacity.523*524* @param {Element} element Dom Node to be used in the animation.525* @param {number} time Length of animation in milliseconds.526* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.527* @extends {goog.fx.dom.Fade}528* @constructor529* @struct530*/531goog.fx.dom.FadeIn = function(element, time, opt_acc) {532goog.fx.dom.Fade.call(this, element, 0, 1, time, opt_acc);533};534goog.inherits(goog.fx.dom.FadeIn, goog.fx.dom.Fade);535536537538/**539* Fades an element out from full opacity to completely transparent and then540* sets the display to 'none'541*542* @param {Element} element Dom Node to be used in the animation.543* @param {number} time Length of animation in milliseconds.544* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.545* @extends {goog.fx.dom.Fade}546* @constructor547* @struct548*/549goog.fx.dom.FadeOutAndHide = function(element, time, opt_acc) {550goog.fx.dom.Fade.call(this, element, 1, 0, time, opt_acc);551};552goog.inherits(goog.fx.dom.FadeOutAndHide, goog.fx.dom.Fade);553554555/** @override */556goog.fx.dom.FadeOutAndHide.prototype.onBegin = function() {557this.show();558goog.fx.dom.FadeOutAndHide.superClass_.onBegin.call(this);559};560561562/** @override */563goog.fx.dom.FadeOutAndHide.prototype.onEnd = function() {564this.hide();565goog.fx.dom.FadeOutAndHide.superClass_.onEnd.call(this);566};567568569570/**571* Sets an element's display to be visible and then fades an element in from572* completely transparent to fully opaque.573*574* @param {Element} element Dom Node to be used in the animation.575* @param {number} time Length of animation in milliseconds.576* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.577* @extends {goog.fx.dom.Fade}578* @constructor579* @struct580*/581goog.fx.dom.FadeInAndShow = function(element, time, opt_acc) {582goog.fx.dom.Fade.call(this, element, 0, 1, time, opt_acc);583};584goog.inherits(goog.fx.dom.FadeInAndShow, goog.fx.dom.Fade);585586587/** @override */588goog.fx.dom.FadeInAndShow.prototype.onBegin = function() {589this.show();590goog.fx.dom.FadeInAndShow.superClass_.onBegin.call(this);591};592593594595/**596* Provides a transformation of an elements background-color.597*598* Start and End should be 3D arrays representing R,G,B599*600* @param {Element} element Dom Node to be used in the animation.601* @param {Array<number>} start 3D Array for RGB of start color.602* @param {Array<number>} end 3D Array for RGB of end color.603* @param {number} time Length of animation in milliseconds.604* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.605* @extends {goog.fx.dom.PredefinedEffect}606* @constructor607* @struct608*/609goog.fx.dom.BgColorTransform = function(element, start, end, time, opt_acc) {610if (start.length != 3 || end.length != 3) {611throw Error('Start and end points must be 3D');612}613goog.fx.dom.PredefinedEffect.apply(this, arguments);614};615goog.inherits(goog.fx.dom.BgColorTransform, goog.fx.dom.PredefinedEffect);616617618/**619* Animation event handler that will set the background-color of an element620*/621goog.fx.dom.BgColorTransform.prototype.setColor = function() {622var coordsAsInts = [];623for (var i = 0; i < this.coords.length; i++) {624coordsAsInts[i] = Math.round(this.coords[i]);625}626var color = 'rgb(' + coordsAsInts.join(',') + ')';627this.element.style.backgroundColor = color;628};629630631/** @override */632goog.fx.dom.BgColorTransform.prototype.updateStyle = function() {633this.setColor();634};635636637/**638* Fade elements background color from start color to the element's current639* background color.640*641* Start should be a 3D array representing R,G,B642*643* @param {Element} element Dom Node to be used in the animation.644* @param {Array<number>} start 3D Array for RGB of start color.645* @param {number} time Length of animation in milliseconds.646* @param {goog.events.EventHandler=} opt_eventHandler Optional event handler647* to use when listening for events.648*/649goog.fx.dom.bgColorFadeIn = function(element, start, time, opt_eventHandler) {650var initialBgColor = element.style.backgroundColor || '';651var computedBgColor = goog.style.getBackgroundColor(element);652var end;653654if (computedBgColor && computedBgColor != 'transparent' &&655computedBgColor != 'rgba(0, 0, 0, 0)') {656end = goog.color.hexToRgb(goog.color.parse(computedBgColor).hex);657} else {658end = [255, 255, 255];659}660661var anim = new goog.fx.dom.BgColorTransform(element, start, end, time);662663function setBgColor() { element.style.backgroundColor = initialBgColor; }664665if (opt_eventHandler) {666opt_eventHandler.listen(anim, goog.fx.Transition.EventType.END, setBgColor);667} else {668goog.events.listen(anim, goog.fx.Transition.EventType.END, setBgColor);669}670671anim.play();672};673674675676/**677* Provides a transformation of an elements color.678*679* @param {Element} element Dom Node to be used in the animation.680* @param {Array<number>} start 3D Array representing R,G,B.681* @param {Array<number>} end 3D Array representing R,G,B.682* @param {number} time Length of animation in milliseconds.683* @param {Function=} opt_acc Acceleration function, returns 0-1 for inputs 0-1.684* @constructor685* @struct686* @extends {goog.fx.dom.PredefinedEffect}687*/688goog.fx.dom.ColorTransform = function(element, start, end, time, opt_acc) {689if (start.length != 3 || end.length != 3) {690throw Error('Start and end points must be 3D');691}692goog.fx.dom.PredefinedEffect.apply(this, arguments);693};694goog.inherits(goog.fx.dom.ColorTransform, goog.fx.dom.PredefinedEffect);695696697/**698* Animation event handler that will set the color of an element.699* @protected700* @override701*/702goog.fx.dom.ColorTransform.prototype.updateStyle = function() {703var coordsAsInts = [];704for (var i = 0; i < this.coords.length; i++) {705coordsAsInts[i] = Math.round(this.coords[i]);706}707var color = 'rgb(' + coordsAsInts.join(',') + ')';708this.element.style.color = color;709};710711712