Skip to content

Commit

Permalink
Added support for synthetic animation/transition events.
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj committed Feb 26, 2016
1 parent 4a1b0b7 commit 260353e
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 43 deletions.
37 changes: 37 additions & 0 deletions docs/docs/ref-05-events.md
Expand Up @@ -139,6 +139,7 @@ DOMEventTarget relatedTarget

These focus events work on all elements in the React DOM, not just form elements.


### Form Events

Event names:
Expand Down Expand Up @@ -246,6 +247,7 @@ number deltaY
number deltaZ
```


### Media Events

Event names:
Expand All @@ -254,10 +256,45 @@ Event names:
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting
```


### Image Events

Event names:

```
onLoad onError
```


### Animation Events

Event names:

```
onAnimationStart onAnimationEnd onAnimationIteration
```

Properties:

```javascript
string animationName
string pseudoElement
float elapsedTime
```


### Transition Events

Event names:

```
onTransitionEnd
```

Properties:

```javascript
string propertyName
string pseudoElement
float elapsedTime
```
50 changes: 7 additions & 43 deletions src/addons/transitions/ReactTransitionEvents.js
Expand Up @@ -13,56 +13,20 @@

var ExecutionEnvironment = require('ExecutionEnvironment');

/**
* EVENT_NAME_MAP is used to determine which event fired when a
* transition/animation ends, based on the style property used to
* define that event.
*/
var EVENT_NAME_MAP = {
transitionend: {
'transition': 'transitionend',
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'mozTransitionEnd',
'OTransition': 'oTransitionEnd',
'msTransition': 'MSTransitionEnd',
},

animationend: {
'animation': 'animationend',
'WebkitAnimation': 'webkitAnimationEnd',
'MozAnimation': 'mozAnimationEnd',
'OAnimation': 'oAnimationEnd',
'msAnimation': 'MSAnimationEnd',
},
};
var getVendorPrefixedEventName = require('getVendorPrefixedEventName');

var endEvents = [];

function detectEvents() {
var testEl = document.createElement('div');
var style = testEl.style;

// On some platforms, in particular some releases of Android 4.x,
// the un-prefixed "animation" and "transition" properties are defined on the
// style object but the events that fire will still be prefixed, so we need
// to check if the un-prefixed events are useable, and if not remove them
// from the map
if (!('AnimationEvent' in window)) {
delete EVENT_NAME_MAP.animationend.animation;
}
var animEnd = getVendorPrefixedEventName('animationend');
var transEnd = getVendorPrefixedEventName('transitionend');

if (!('TransitionEvent' in window)) {
delete EVENT_NAME_MAP.transitionend.transition;
if (animEnd) {
endEvents.push(animEnd);
}

for (var baseEventName in EVENT_NAME_MAP) {
var baseEvents = EVENT_NAME_MAP[baseEventName];
for (var styleName in baseEvents) {
if (styleName in style) {
endEvents.push(baseEvents[styleName]);
break;
}
}
if (transEnd) {
endEvents.push(transEnd);
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/renderers/dom/client/ReactBrowserEventEmitter.js
Expand Up @@ -17,6 +17,7 @@ var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ViewportMetrics = require('ViewportMetrics');

var assign = require('Object.assign');
var getVendorPrefixedEventName = require('getVendorPrefixedEventName');
var isEventSupported = require('isEventSupported');

/**
Expand Down Expand Up @@ -83,6 +84,9 @@ var reactTopListenersCounter = 0;
// events so we don't include them here
var topEventMapping = {
topAbort: 'abort',
topAnimationEnd: getVendorPrefixedEventName('animationend') || 'animationend',
topAnimationIteration: getVendorPrefixedEventName('animationiteration') || 'animationiteration',
topAnimationStart: getVendorPrefixedEventName('animationstart') || 'animationstart',
topBlur: 'blur',
topCanPlay: 'canplay',
topCanPlayThrough: 'canplaythrough',
Expand Down Expand Up @@ -139,6 +143,7 @@ var topEventMapping = {
topTouchEnd: 'touchend',
topTouchMove: 'touchmove',
topTouchStart: 'touchstart',
topTransitionEnd: getVendorPrefixedEventName('transitionend') || 'transitionend',
topVolumeChange: 'volumechange',
topWaiting: 'waiting',
topWheel: 'wheel',
Expand Down
38 changes: 38 additions & 0 deletions src/renderers/dom/client/eventPlugins/SimpleEventPlugin.js
Expand Up @@ -15,13 +15,15 @@ var EventConstants = require('EventConstants');
var EventListener = require('EventListener');
var EventPropagators = require('EventPropagators');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var SyntheticAnimationEvent = require('SyntheticAnimationEvent');
var SyntheticClipboardEvent = require('SyntheticClipboardEvent');
var SyntheticEvent = require('SyntheticEvent');
var SyntheticFocusEvent = require('SyntheticFocusEvent');
var SyntheticKeyboardEvent = require('SyntheticKeyboardEvent');
var SyntheticMouseEvent = require('SyntheticMouseEvent');
var SyntheticDragEvent = require('SyntheticDragEvent');
var SyntheticTouchEvent = require('SyntheticTouchEvent');
var SyntheticTransitionEvent = require('SyntheticTransitionEvent');
var SyntheticUIEvent = require('SyntheticUIEvent');
var SyntheticWheelEvent = require('SyntheticWheelEvent');

Expand All @@ -39,6 +41,24 @@ var eventTypes = {
captured: keyOf({onAbortCapture: true}),
},
},
animationEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onAnimationEnd: true}),
captured: keyOf({onAnimationEndCapture: true}),
},
},
animationIteration: {
phasedRegistrationNames: {
bubbled: keyOf({onAnimationIteration: true}),
captured: keyOf({onAnimationIterationCapture: true}),
},
},
animationStart: {
phasedRegistrationNames: {
bubbled: keyOf({onAnimationStart: true}),
captured: keyOf({onAnimationStartCapture: true}),
},
},
blur: {
phasedRegistrationNames: {
bubbled: keyOf({onBlur: true}),
Expand Down Expand Up @@ -365,6 +385,12 @@ var eventTypes = {
captured: keyOf({onTouchStartCapture: true}),
},
},
transitionEnd: {
phasedRegistrationNames: {
bubbled: keyOf({onTransitionEnd: true}),
captured: keyOf({onTransitionEndCapture: true}),
},
},
volumeChange: {
phasedRegistrationNames: {
bubbled: keyOf({onVolumeChange: true}),
Expand All @@ -387,6 +413,9 @@ var eventTypes = {

var topLevelEventsToDispatchConfig = {
topAbort: eventTypes.abort,
topAnimationEnd: eventTypes.animationEnd,
topAnimationIteration: eventTypes.animationIteration,
topAnimationStart: eventTypes.animationStart,
topBlur: eventTypes.blur,
topCanPlay: eventTypes.canPlay,
topCanPlayThrough: eventTypes.canPlayThrough,
Expand Down Expand Up @@ -441,6 +470,7 @@ var topLevelEventsToDispatchConfig = {
topTouchEnd: eventTypes.touchEnd,
topTouchMove: eventTypes.touchMove,
topTouchStart: eventTypes.touchStart,
topTransitionEnd: eventTypes.transitionEnd,
topVolumeChange: eventTypes.volumeChange,
topWaiting: eventTypes.waiting,
topWheel: eventTypes.wheel,
Expand Down Expand Up @@ -549,6 +579,14 @@ var SimpleEventPlugin = {
case topLevelTypes.topTouchStart:
EventConstructor = SyntheticTouchEvent;
break;
case topLevelTypes.topAnimationEnd:
case topLevelTypes.topAnimationIteration:
case topLevelTypes.topAnimationStart:
EventConstructor = SyntheticAnimationEvent;
break;
case topLevelTypes.topTransitionEnd:
EventConstructor = SyntheticTransitionEvent;
break;
case topLevelTypes.topScroll:
EventConstructor = SyntheticUIEvent;
break;
Expand Down
@@ -0,0 +1,47 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule SyntheticAnimationEvent
*/

'use strict';

var SyntheticEvent = require('SyntheticEvent');

/**
* @interface Event
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
*/
var AnimationEventInterface = {
animationName: null,
elapsedTime: null,
pseudoElement: null,
};

/**
* @param {object} dispatchConfig Configuration used to dispatch this event.
* @param {string} dispatchMarker Marker identifying the event target.
* @param {object} nativeEvent Native browser event.
* @extends {SyntheticEvent}
*/
function SyntheticAnimationEvent(
dispatchConfig,
dispatchMarker,
nativeEvent,
nativeEventTarget
) {
return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
}

SyntheticEvent.augmentClass(
SyntheticAnimationEvent,
AnimationEventInterface
);

module.exports = SyntheticAnimationEvent;
@@ -0,0 +1,47 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule SyntheticTransitionEvent
*/

'use strict';

var SyntheticEvent = require('SyntheticEvent');

/**
* @interface Event
* @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
* @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
*/
var TransitionEventInterface = {
propertyName: null,
elapsedTime: null,
pseudoElement: null,
};

/**
* @param {object} dispatchConfig Configuration used to dispatch this event.
* @param {string} dispatchMarker Marker identifying the event target.
* @param {object} nativeEvent Native browser event.
* @extends {SyntheticEvent}
*/
function SyntheticTransitionEvent(
dispatchConfig,
dispatchMarker,
nativeEvent,
nativeEventTarget
) {
return SyntheticEvent.call(this, dispatchConfig, dispatchMarker, nativeEvent, nativeEventTarget);
}

SyntheticEvent.augmentClass(
SyntheticTransitionEvent,
TransitionEventInterface
);

module.exports = SyntheticTransitionEvent;

0 comments on commit 260353e

Please sign in to comment.