autoAnimateElements( from, to, elementOptions, animationOptions, id ) {

		// 'from' elements are given a data-auto-animate-target with no value,
		// 'to' elements are are given a data-auto-animate-target with an ID
		from.dataset.autoAnimateTarget = '';
		to.dataset.autoAnimateTarget = id;

		// Each element may override any of the auto-animate options
		// like transition easing, duration and delay via data-attributes
		let options = this.getAutoAnimateOptions( to, animationOptions );

		// If we're using a custom element matcher the element options
		// may contain additional transition overrides
		if( typeof elementOptions.delay !== 'undefined' ) options.delay = elementOptions.delay;
		if( typeof elementOptions.duration !== 'undefined' ) options.duration = elementOptions.duration;
		if( typeof elementOptions.easing !== 'undefined' ) options.easing = elementOptions.easing;

		let fromProps = this.getAutoAnimatableProperties( 'from', from, elementOptions ),
			toProps = this.getAutoAnimatableProperties( 'to', to, elementOptions );

		if( to.classList.contains( 'fragment' ) ) {

			// Don't auto-animate the opacity of fragments to avoid
			// conflicts with fragment animations
			delete toProps.styles['opacity'];

		}

		// If translation and/or scaling are enabled, css transform
		// the 'to' element so that it matches the position and size
		// of the 'from' element
		if( elementOptions.translate !== false || elementOptions.scale !== false ) {

			let presentationScale = this.Reveal.getScale();

			let delta = {
				x: ( fromProps.x - toProps.x ) / presentationScale,
				y: ( fromProps.y - toProps.y ) / presentationScale,
				scaleX: fromProps.width / toProps.width,
				scaleY: fromProps.height / toProps.height
			};

			// Limit decimal points to avoid 0.0001px blur and stutter
			delta.x = Math.round( delta.x * 1000 ) / 1000;
			delta.y = Math.round( delta.y * 1000 ) / 1000;
			delta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000;
			delta.scaleX = Math.round( delta.scaleX * 1000 ) / 1000;

			let translate = elementOptions.translate !== false && ( delta.x !== 0 || delta.y !== 0 ),
				scale = elementOptions.scale !== false && ( delta.scaleX !== 0 || delta.scaleY !== 0 );

			// No need to transform if nothing's changed
			if( translate || scale ) {

				let transform = [];

				if( translate ) transform.push( `translate(${delta.x}px, ${delta.y}px)` );
				if( scale ) transform.push( `scale(${delta.scaleX}, ${delta.scaleY})` );

				fromProps.styles['transform'] = transform.join( ' ' );
				fromProps.styles['transform-origin'] = 'top left';

				toProps.styles['transform'] = 'none';

			}

		}

		// Delete all unchanged 'to' styles
		for( let propertyName in toProps.styles ) {
			const toValue = toProps.styles[propertyName];
			const fromValue = fromProps.styles[propertyName];

			if( toValue === fromValue ) {
				delete toProps.styles[propertyName];
			}
			else {
				// If these property values were set via a custom matcher providing
				// an explicit 'from' and/or 'to' value, we always inject those values.
				if( toValue.explicitValue === true ) {
					toProps.styles[propertyName] = toValue.value;
				}

				if( fromValue.explicitValue === true ) {
					fromProps.styles[propertyName] = fromValue.value;
				}
			}
		}

		let css = '';

		let toStyleProperties = Object.keys( toProps.styles );

		// Only create animate this element IF at least one style
		// property has changed
		if( toStyleProperties.length > 0 ) {

			// Instantly move to the 'from' state
			fromProps.styles['transition'] = 'none';

			// Animate towards the 'to' state
			toProps.styles['transition'] = `all ${options.duration}s ${options.easing} ${options.delay}s`;
			toProps.styles['transition-property'] = toStyleProperties.join( ', ' );
			toProps.styles['will-change'] = toStyleProperties.join( ', ' );

			// Build up our custom CSS. We need to override inline styles
			// so we need to make our styles vErY IMPORTANT!1!!
			let fromCSS = Object.keys( fromProps.styles ).map( propertyName => {
				return propertyName + ': ' + fromProps.styles[propertyName] + ' !important;';
			} ).join( '' );

			let toCSS = Object.keys( toProps.styles ).map( propertyName => {
				return propertyName + ': ' + toProps.styles[propertyName] + ' !important;';
			} ).join( '' );

			css = 	'[data-auto-animate-target="'+ id +'"] {'+ fromCSS +'}' +
					'[data-auto-animate="running"] [data-auto-animate-target="'+ id +'"] {'+ toCSS +'}';

		}

		return css;

	}
Creates a FLIP animation where the `to` element starts out
in the `from` element position and animates to its original
state.

@param {HTMLElement} from
@param {HTMLElement} to
@param {Object} elementOptions Options for this element pair
@param {Object} animationOptions Options set at the slide level
@param {String} id Unique ID that we can use to identify this
auto-animate element in the DOM