source: Dev/trunk/src/client/dojox/css3/transition.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

  • Property svn:executable set to *
File size: 11.6 KB
Line 
1define(["dojo/_base/lang",
2                "dojo/_base/array",
3                "dojo/Deferred",
4                "dojo/when",
5                "dojo/promise/all",
6                "dojo/on",
7                "dojo/sniff"],
8                function(lang, array, Deferred, when, all, on, has){
9        // module:
10        //              dojox/css3/transition
11       
12        //create cross platform animation/transition effects
13        //TODO enable opera mobile when it is hardware accelerated
14        // IE10 is using standard names so this is working without any modifications
15        var transitionEndEventName = "transitionend";
16        var transitionPrefix = "t"; //by default use "t" prefix and "ransition" to make word "transition"
17        var translateMethodStart = "translate3d(";//Android 2.x does not support translateX in CSS Transition, we need to use translate3d in webkit browsers
18        var translateMethodEnd = ",0,0)";
19        if(has("webkit")){
20                transitionPrefix = "WebkitT";
21                transitionEndEventName = "webkitTransitionEnd";
22        }else if(has("mozilla")){
23                transitionPrefix = "MozT";
24                translateMethodStart = "translateX(";
25                translateMethodEnd = ")";
26        }
27       
28        //TODO find a way to lock the animation and prevent animation conflict
29        //Use the simple object inheritance
30        var transition = function(/*Object?*/args){
31                // summary:
32                //              This module defines the transition utilities which can be used
33                //              to perform transition effects based on the CSS Transition standard.
34                // args:
35                //              The arguments which will be mixed into this transition object.
36               
37                //default config should be in animation object itself instead of its prototype
38                //otherwise, it might be easy for making mistake of modifying prototype
39                var defaultConfig = {
40                                startState: {},
41                                endState: {},
42                                node: null,
43                                duration: 250,
44                                "in": true,
45                                direction: 1,
46                                autoClear: true
47                };
48
49                lang.mixin(this, defaultConfig);
50                lang.mixin(this, args);
51
52                //create the deferred object which will resolve after the animation is finished.
53                //We can rely on "onAfterEnd" function to notify the end of a single animation,
54                //but using a deferred object is easier to wait for multiple animations end.
55                if(!this.deferred){
56                        this.deferred = new Deferred();
57                }
58        };
59       
60        lang.extend(transition, {
61               
62                play: function(){
63                        // summary:
64                        //              Plays the transition effect defined by this transition object.
65                        transition.groupedPlay([this]);
66                },
67               
68                //method to apply the state of the transition
69                _applyState: function(state){
70                        var style = this.node.style;
71                        for(var property in state){
72                                if(state.hasOwnProperty(property)){
73                                        style[property] = state[property];
74                                }
75                        }
76                },
77               
78               
79                initState: function(){
80                        // summary:
81                        //              Method to initialize the state for a transition.
82                       
83                        //apply the immediate style change for initial state.
84                        this.node.style[transitionPrefix + "ransitionProperty"] = "none";
85                        this.node.style[transitionPrefix + "ransitionDuration"] = "0ms";
86                        this._applyState(this.startState);
87                       
88                },
89               
90                _beforeStart: function(){
91                        if (this.node.style.display === "none"){
92                                this.node.style.display = "";
93                        }
94                        this.beforeStart();
95                },
96               
97                _beforeClear: function(){
98                        this.node.style[transitionPrefix + "ransitionProperty"] = "";
99                        this.node.style[transitionPrefix + "ransitionDuration"] = "";
100                        if(this["in"] !== true){
101                                this.node.style.display = "none";
102                        }                       
103                        this.beforeClear();
104                },
105               
106                _onAfterEnd: function(){
107                        this.deferred.resolve(this.node);
108                        if(this.node.id && transition.playing[this.node.id]===this.deferred){
109                                delete transition.playing[this.node.id];
110                        }
111                        this.onAfterEnd();
112                },
113               
114                beforeStart: function(){
115                        // summary:
116                        //              The callback which will be called right before the start
117                        //              of the transition effect.
118                },
119               
120                beforeClear: function(){
121                        // summary:
122                        //              The callback which will be called right after the end
123                        //              of the transition effect and before the final state is
124                        //              cleared.
125                },
126               
127                onAfterEnd: function(){
128                        // summary:
129                        //              The callback which will be called right after the end
130                        //              of the transition effect and after the final state is
131                        //              cleared.
132                },
133               
134                start: function(){
135                        // summary:
136                        //              Method to start the transition.
137                        this._beforeStart();
138                        this._startTime = new Date().getTime(); // set transition start timestamp
139                        this._cleared = false; // set clear flag to false
140
141                        var self = this;
142                        //change the transition duration
143                        self.node.style[transitionPrefix + "ransitionProperty"] = "all";
144                        self.node.style[transitionPrefix + "ransitionDuration"] = self.duration + "ms";
145                       
146                        //connect to clear the transition state after the transition end.
147                        //Since the transition is conducted asynchronously, we need to
148                        //connect to transition end event to clear the state
149                        on.once(self.node, transitionEndEventName, function(){
150                                self.clear();
151                        });
152                       
153                        this._applyState(this.endState);
154                },
155               
156                clear: function(){
157                        // summary:
158                        //              Method to clear the state after a transition.
159                        if(this._cleared) {
160                                return;
161                        }
162                        this._cleared = true; // set clear flag to true
163
164                        this._beforeClear();
165                        this._removeState(this.endState);
166                        // console.log(this.node.id + " clear.");
167                        this._onAfterEnd();
168                },
169               
170                //create removeState method
171                _removeState: function(state){
172                        var style = this.node.style;
173                        for(var property in state){
174                                if(state.hasOwnProperty(property)){
175                                        style[property] = "";
176                                }
177                        }
178                }
179               
180        });
181       
182        //TODO add the lock mechanism for all of the transition effects
183        //         consider using only one object for one type of transition.
184       
185        transition.slide = function(node, config){
186                // summary:
187                //              Method which is used to create the transition object of a slide effect.
188                // node:
189                //              The node that the slide transition effect will be applied on.
190                // config:
191                //              The cofig arguments which will be mixed into this transition object.
192
193                //create the return object and set the startState, endState of the return
194                var ret = new transition(config);
195                ret.node = node;
196               
197                var startX = "0";
198                var endX = "0";
199               
200                if(ret["in"]){
201                        if(ret.direction === 1){
202                                startX = "100%";
203                        }else{
204                                startX = "-100%";
205                        }
206                }else{
207                        if(ret.direction === 1){
208                                endX = "-100%";
209                        }else{
210                                endX = "100%";
211                        }
212                }
213               
214                ret.startState[transitionPrefix + "ransform"]=translateMethodStart+startX+translateMethodEnd;
215               
216                ret.endState[transitionPrefix + "ransform"]=translateMethodStart+endX+translateMethodEnd;
217               
218                return ret;
219        };
220               
221        transition.fade = function(node, config){
222                // summary:
223                //              Method which is used to create the transition object of fade effect.
224                // node:
225                //              The node that the fade transition effect will be applied on.
226                // config:
227                //              The cofig arguments which will be mixed into this transition object.
228                var ret = new transition(config);
229                ret.node = node;
230               
231                var startOpacity = "0";
232                var endOpacity = "0";
233               
234                if(ret["in"]){
235                        endOpacity = "1";
236                }else{
237                        startOpacity = "1";
238                }
239               
240                lang.mixin(ret, {
241                        startState:{
242                                "opacity": startOpacity
243                        },
244                        endState:{
245                                "opacity": endOpacity
246                        }
247                });
248               
249                return ret;
250        };
251       
252        transition.flip = function(node, config){
253                // summary:
254                //              Method which is used to create the transition object of flip effect.
255                // node:
256                //              The node that the flip transition effect will be applied on.
257                // config:
258                //              The cofig arguments which will be mixed into this transition object.
259               
260                var ret = new transition(config);
261                ret.node = node;
262           
263                if(ret["in"]){
264                        //Need to set opacity here because Android 2.2 has bug that
265                        //scale(...) in transform does not persist status
266                        lang.mixin(ret,{
267                                startState:{
268                                        "opacity": "0"
269                                },
270                                endState:{
271                                        "opacity": "1"
272                                }
273                        });
274                        ret.startState[transitionPrefix + "ransform"]="scale(0,0.8) skew(0,-30deg)";
275                        ret.endState[transitionPrefix + "ransform"]="scale(1,1) skew(0,0)";
276                }else{
277                        lang.mixin(ret,{
278                                startState:{
279                                        "opacity": "1"
280                                },
281                                endState:{
282                                        "opacity": "0"
283                                }
284                        });                     
285                        ret.startState[transitionPrefix + "ransform"]="scale(1,1) skew(0,0)";
286                        ret.endState[transitionPrefix + "ransform"]="scale(0,0.8) skew(0,30deg)";
287                }
288               
289                return ret;
290        };
291       
292        var getWaitingList = function(/*Array*/ nodes){
293                var defs = [];
294                array.forEach(nodes, function(node){
295                        //check whether the node is under other animation
296                        if(node.id && transition.playing[node.id]){
297                                //hook on deferred object in transition.playing
298                                defs.push(transition.playing[node.id]);
299                        }
300                       
301                });
302                return all(defs);
303        };
304       
305        transition.getWaitingList = getWaitingList;
306       
307        transition.groupedPlay = function(/*Array*/args){
308                // summary:
309                //              The method which groups multiple transitions and plays
310                //              them together.
311                // args:
312                //              The array of transition objects which will be played together.
313               
314                var animNodes = array.filter(args, function(item){
315                        return item.node;
316                });
317               
318                var waitingList = getWaitingList(animNodes);
319
320                //update registry with deferred objects in animations of args.
321                array.forEach(args, function(item){
322                        if(item.node.id){
323                                transition.playing[item.node.id] = item.deferred;
324                        }
325                });
326               
327                //wait for all deferred object in deferred list to resolve
328                when(waitingList, function(){
329                        array.forEach(args, function(item){
330                                //set the start state
331                                item.initState();
332                        });
333                       
334                        //Assume the fps of the animation should be higher than 30 fps and
335                        //allow the browser to use one frame's time to redraw so that
336                        //the transition can be started
337                        setTimeout(function(){
338                                array.forEach(args, function(item){
339                                        item.start();
340                                });
341
342                                // check and clear node if the node not cleared.
343                                // 1. on Android2.2/2.3, the "fade out" transitionEnd event will be lost if the soft keyboard popup, so we need to check nodes' clear status.
344                                // 2. The "fade in" transitionEnd event will before or after "fade out" transitionEnd event and it always occurs.
345                                //        We can check fade out node status in the last "fade in" node transitionEnd event callback, if node transition timeout, we clear it.
346                                // NOTE: the last "fade in" transitionEnd event will always fired, so we bind on this event and check other nodes.
347                                on.once(args[args.length-1].node, transitionEndEventName, function(){
348                                        var timeout;
349                                        for(var i=0; i<args.length-1; i++){
350                                                if(args[i].deferred.fired !== 0 && !args[i]._cleared){
351                                                        timeout = new Date().getTime() - args[i]._startTime;
352                                                        if(timeout >= args[i].duration){
353                                                                args[i].clear();
354                                                        }
355                                                }
356                                        }
357                                });
358                                setTimeout(function(){
359                                        var timeout;
360                                        for(var i=0; i<args.length; i++){
361                                                if(args[i].deferred.fired !== 0 && !args[i]._cleared){
362                                                        timeout = new Date().getTime() - args[i]._startTime;
363                                                        if(timeout >= args[i].duration){
364                                                                args[i].clear();
365                                                        }
366                                                }
367                                        }
368                                }, args[0].duration+50);
369                        }, 33);
370                });
371        };
372       
373        transition.chainedPlay = function(/*Array*/args){
374                // summary:
375                //              The method which plays multiple transitions one by one.
376                // args:
377                //              The array of transition objects which will be played in a chain.
378               
379                var animNodes = array.filter(args, function(item){
380                        return item.node;
381                });
382               
383                var waitingList = getWaitingList(animNodes);
384
385                //update registry with deferred objects in animations of args.
386                array.forEach(args, function(item){
387                        if(item.node.id){
388                                transition.playing[item.node.id] = item.deferred;
389                        }
390                });
391               
392                when(waitingList, function(){
393                        array.forEach(args, function(item){
394                                //set the start state
395                                item.initState();
396                        });
397                       
398                        //chain animations together
399                        for (var i=1, len=args.length; i < len; i++){
400                                args[i-1].deferred.then(lang.hitch(args[i], function(){
401                                        this.start();
402                                }));
403                        }
404                       
405                        //Assume the fps of the animation should be higher than 30 fps and
406                        //allow the browser to use one frame's time to redraw so that
407                        //the transition can be started
408                        setTimeout(function(){
409                                args[0].start();
410                        }, 33);
411                });               
412        };
413       
414        //TODO complete the registry mechanism for animation handling and prevent animation conflicts
415        transition.playing = {};
416       
417        return transition;
418});
Note: See TracBrowser for help on using the repository browser.