refactored and improved auto-animate matcher, supports line-by-line code animations
							parent
							
								
									345ec01f19
								
							
						
					
					
						commit
						e2a2c2c022
					
				|  | @ -1233,6 +1233,9 @@ body { | |||
| /********************************************* | ||||
|  * AUTO ANIMATE | ||||
|  *********************************************/ | ||||
| .reveal [data-auto-animate-target="unmatched"] { | ||||
|   will-change: opacity; } | ||||
| 
 | ||||
| .reveal section[data-auto-animate]:not(.stack):not([data-auto-animate="running"]) [data-auto-animate-target="unmatched"] { | ||||
|   opacity: 0; } | ||||
| 
 | ||||
|  | @ -1515,6 +1518,12 @@ body { | |||
|   width: 100%; | ||||
|   box-sizing: border-box; } | ||||
| 
 | ||||
| .reveal pre[data-auto-animate-target] { | ||||
|   overflow: hidden; } | ||||
| 
 | ||||
| .reveal pre[data-auto-animate-target] code { | ||||
|   height: 100%; } | ||||
| 
 | ||||
| /********************************************* | ||||
|  * ROLLING LINKS | ||||
|  *********************************************/ | ||||
|  |  | |||
|  | @ -1318,6 +1318,10 @@ $controlsArrowAngleActive: 36deg; | |||
|  * AUTO ANIMATE | ||||
|  *********************************************/ | ||||
| 
 | ||||
| .reveal [data-auto-animate-target="unmatched"] { | ||||
| 	will-change: opacity; | ||||
| } | ||||
| 
 | ||||
| .reveal section[data-auto-animate]:not(.stack):not([data-auto-animate="running"]) [data-auto-animate-target="unmatched"] { | ||||
| 	opacity: 0; | ||||
| } | ||||
|  | @ -1663,6 +1667,13 @@ $controlsArrowAngleActive: 36deg; | |||
| 	box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .reveal pre[data-auto-animate-target] { | ||||
| 	overflow: hidden; | ||||
| } | ||||
| .reveal pre[data-auto-animate-target] code { | ||||
| 	height: 100%; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /********************************************* | ||||
|  * ROLLING LINKS | ||||
|  |  | |||
							
								
								
									
										163
									
								
								js/reveal.js
								
								
								
								
							
							
						
						
									
										163
									
								
								js/reveal.js
								
								
								
								
							|  | @ -3058,7 +3058,7 @@ | |||
| 		cueAutoSlide(); | ||||
| 
 | ||||
| 		// Auto-animation
 | ||||
| 		if( slideChanged && previousSlide && currentSlide ) { | ||||
| 		if( slideChanged && previousSlide && currentSlide && !isOverview() ) { | ||||
| 
 | ||||
| 			// Skip the slide transition between our two slides
 | ||||
| 			// when auto-animating individual elements
 | ||||
|  | @ -4028,6 +4028,7 @@ | |||
| 			// 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!!
 | ||||
|  | @ -4162,15 +4163,18 @@ | |||
| 	 */ | ||||
| 	function getAutoAnimatableElements( fromSlide, toSlide ) { | ||||
| 
 | ||||
| 		var matcher = typeof config.autoAnimateMatcher === 'function' ? config.autoAnimateMatcher : findAutoAnimatePairs; | ||||
| 		var matcher = typeof config.autoAnimateMatcher === 'function' ? config.autoAnimateMatcher : getAutoAnimatePairs; | ||||
| 
 | ||||
| 		var pairs = matcher( fromSlide, toSlide ); | ||||
| 
 | ||||
| 		var reserved = []; | ||||
| 
 | ||||
| 		// Remove duplicate pairs
 | ||||
| 		return pairs.filter( function( pair, index ) { | ||||
| 			return index === pairs.findIndex( function( comparePair ) { | ||||
| 				return pair.from === comparePair.from && pair.to === comparePair.to; | ||||
| 			} ); | ||||
| 			if( reserved.indexOf( pair.to ) === -1 ) { | ||||
| 				reserved.push( pair.to ); | ||||
| 				return true; | ||||
| 			} | ||||
| 		} ); | ||||
| 
 | ||||
| 	} | ||||
|  | @ -4181,64 +4185,67 @@ | |||
| 	 * You can specify a custom matcher function by using | ||||
| 	 * the `autoAnimateMatcher` config option. | ||||
| 	 */ | ||||
| 	function findAutoAnimatePairs( fromSlide, toSlide ) { | ||||
| 	function getAutoAnimatePairs( fromSlide, toSlide ) { | ||||
| 
 | ||||
| 		var pairs = []; | ||||
| 
 | ||||
| 		var findMatches = function( selector, serializer, transformer ) { | ||||
| 
 | ||||
| 			var fromHash = {}; | ||||
| 
 | ||||
| 			toArray( fromSlide.querySelectorAll( selector ) ).forEach( function( element ) { | ||||
| 				if( typeof transformer === 'function' ) element = transformer( element ); | ||||
| 				fromHash[ serializer( element ) ] = element; | ||||
| 			} ); | ||||
| 
 | ||||
| 			toArray( toSlide.querySelectorAll( selector ) ).forEach( function( element ) { | ||||
| 				if( typeof transformer === 'function' ) element = transformer( element ); | ||||
| 				var fromElement = fromHash[ serializer( element ) ]; | ||||
| 				if( fromElement ) { | ||||
| 					pairs.push({ from: fromElement, to: element }); | ||||
| 				} | ||||
| 			} ); | ||||
| 
 | ||||
| 		}; | ||||
| 
 | ||||
| 		var textNodes = 'h1, h2, h3, h4, h5, h6, p, li, span'; | ||||
| 		var codeNodes = 'pre'; | ||||
| 		var textNodes = 'h1, h2, h3, h4, h5, h6, p, li'; | ||||
| 		var mediaNodes = 'img, video, iframe'; | ||||
| 
 | ||||
| 		// Eplicit matches via data-id
 | ||||
| 		findMatches( '[data-id]', function( node ) { | ||||
| 		findAutoAnimateMatches( pairs, fromSlide, toSlide, '[data-id]', function( node ) { | ||||
| 			return node.nodeName + ':::' + node.getAttribute( 'data-id' ); | ||||
| 		} ); | ||||
| 
 | ||||
| 		// Text
 | ||||
| 		findMatches( textNodes, function( node ) { | ||||
| 		findAutoAnimateMatches( pairs, fromSlide, toSlide, textNodes, function( node ) { | ||||
| 			return node.nodeName + ':::' + node.innerText; | ||||
| 		}, null ); | ||||
| 		} ); | ||||
| 
 | ||||
| 		// Media
 | ||||
| 		findMatches( mediaNodes, function( node ) { | ||||
| 		findAutoAnimateMatches( pairs, fromSlide, toSlide, mediaNodes, function( node ) { | ||||
| 			return node.nodeName + ':::' + ( node.getAttribute( 'src' ) || node.getAttribute( 'data-src' ) ); | ||||
| 		} ); | ||||
| 
 | ||||
| 		// Code
 | ||||
| 		findMatches( 'pre>code', function( node ) { | ||||
| 		findAutoAnimateMatches( pairs, fromSlide, toSlide, codeNodes, function( node ) { | ||||
| 			return node.nodeName + ':::' + node.innerText; | ||||
| 		}, function( element ) { | ||||
| 			return element.parentNode; | ||||
| 		} ); | ||||
| 
 | ||||
| 		pairs.forEach( function( pair ) { | ||||
| 
 | ||||
| 			var fromElement = pair.from; | ||||
| 			var matchesMethod = fromElement.matches || fromElement.matchesSelector || fromElement.msMatchesSelector; | ||||
| 
 | ||||
| 			// Disable scale transformations on text nodes, we transiition
 | ||||
| 			// each individual text property instead
 | ||||
| 			if( matchesMethod.call( fromElement, textNodes ) ) { | ||||
| 			if( pair.from.matches( textNodes ) ) { | ||||
| 				pair.options = { scale: false }; | ||||
| 			} | ||||
| 			// Animate individual lines of code
 | ||||
| 			else if( pair.from.matches( codeNodes ) ) { | ||||
| 
 | ||||
| 				// Transition the code block's width and height instead of scaling
 | ||||
| 				// to prevent its content from being squished
 | ||||
| 				pair.options = { scale: false, styles: [ 'width', 'height' ] }; | ||||
| 
 | ||||
| 				// Lines of code
 | ||||
| 				findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-code', function( node ) { | ||||
| 					return node.textContent; | ||||
| 				}, { | ||||
| 					scale: false, | ||||
| 					styles: [], | ||||
| 					measure: getLocalBoundingBox | ||||
| 				} ); | ||||
| 
 | ||||
| 				// Line numbers
 | ||||
| 				findAutoAnimateMatches( pairs, pair.from, pair.to, '.hljs .hljs-ln-line[data-line-number]', function( node ) { | ||||
| 					return node.getAttribute( 'data-line-number' ); | ||||
| 				}, { | ||||
| 					scale: false, | ||||
| 					styles: [ 'width' ], | ||||
| 					measure: getLocalBoundingBox | ||||
| 				} ); | ||||
| 
 | ||||
| 			} | ||||
| 
 | ||||
| 		} ); | ||||
| 
 | ||||
|  | @ -4246,6 +4253,88 @@ | |||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Helper method which returns a bounding box based on | ||||
| 	 * the given elements offset coordinates. | ||||
| 	 * | ||||
| 	 * @param {HTMLElement} element | ||||
| 	 * @return {Object} x, y, width, height | ||||
| 	 */ | ||||
| 	function getLocalBoundingBox( element ) { | ||||
| 
 | ||||
| 		var presentationScale = Reveal.getScale(); | ||||
| 
 | ||||
| 		return { | ||||
| 			x: Math.round( ( element.offsetLeft * presentationScale ) * 100 ) / 100, | ||||
| 			y: Math.round( ( element.offsetTop * presentationScale ) * 100 ) / 100, | ||||
| 			width: Math.round( ( element.offsetWidth * presentationScale ) * 100 ) / 100, | ||||
| 			height: Math.round( ( element.offsetHeight * presentationScale ) * 100 ) / 100 | ||||
| 		}; | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Finds matching elements between two slides. | ||||
| 	 * | ||||
| 	 * @param {Array} pairs            	List of pairs to push matches to | ||||
| 	 * @param {HTMLElement} fromScope   Scope within the from element exists | ||||
| 	 * @param {HTMLElement} toScope     Scope within the to element exists | ||||
| 	 * @param {String} selector         CSS selector of the element to match | ||||
| 	 * @param {Function} serializer     A function that accepts an element and returns | ||||
| 	 *                                  a stringified ID based on its contents | ||||
| 	 * @param {Object} animationOptions Optional config options for this pair | ||||
| 	 */ | ||||
| 	function findAutoAnimateMatches( pairs, fromScope, toScope, selector, serializer, animationOptions ) { | ||||
| 
 | ||||
| 		var fromMatches = {}; | ||||
| 		var toMatches = {}; | ||||
| 
 | ||||
| 		[].slice.call( fromScope.querySelectorAll( selector ) ).forEach( function( element, i ) { | ||||
| 			var key = serializer( element ); | ||||
| 			if( typeof key === 'string' && key.length ) { | ||||
| 				fromMatches[key] = fromMatches[key] || []; | ||||
| 				fromMatches[key].push( element ); | ||||
| 			} | ||||
| 		} ); | ||||
| 
 | ||||
| 		[].slice.call( toScope.querySelectorAll( selector ) ).forEach( function( element, i ) { | ||||
| 			var key = serializer( element ); | ||||
| 			toMatches[key] = toMatches[key] || []; | ||||
| 			toMatches[key].push( element ); | ||||
| 
 | ||||
| 			var fromElement; | ||||
| 
 | ||||
| 			// Retrieve the 'from' element
 | ||||
| 			if( fromMatches[key] ) { | ||||
| 				var pimaryIndex = toMatches[key].length - 1; | ||||
| 				var secondaryIndex = fromMatches[key].length - 1; | ||||
| 
 | ||||
| 				// If there are multiple identical from elements, retrieve
 | ||||
| 				// the one at the same index as our to-element.
 | ||||
| 				if( fromMatches[key][ pimaryIndex ] ) { | ||||
| 					fromElement = fromMatches[key][ pimaryIndex ]; | ||||
| 					fromMatches[key][ pimaryIndex ] = null; | ||||
| 				} | ||||
| 				// If there are no matching from-elements at the same index,
 | ||||
| 				// use the last one.
 | ||||
| 				else if( fromMatches[key][ secondaryIndex ] ) { | ||||
| 					fromElement = fromMatches[key][ secondaryIndex ]; | ||||
| 					fromMatches[key][ secondaryIndex ] = null; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// If we've got a matching pair, push it to the list of pairs
 | ||||
| 			if( fromElement ) { | ||||
| 				pairs.push({ | ||||
| 					from: fromElement, | ||||
| 					to: element, | ||||
| 					options: animationOptions | ||||
| 				}); | ||||
| 			} | ||||
| 		} ); | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Returns a all elements within the given scope that should | ||||
| 	 * be considered unmatched in an auto-animate transition. If | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ | |||
| 					<h3>Auto-Animate Example</h3> | ||||
| 					<p>This will fade out</p> | ||||
| 					<img src="assets/image1.png" style="height: 100px;"> | ||||
| 					<pre><code class="hljs" data-trim> | ||||
| 					<pre data-id="code"><code data-line-numbers class="hljs" data-trim> | ||||
| 						function Example() { | ||||
| 						  const [count, setCount] = useState(0); | ||||
| 						} | ||||
|  | @ -34,8 +34,9 @@ | |||
| 					<p style="opacity: 0.2; margin-top: 200px;">This will fade out</p> | ||||
| 					<p>This element is unmatched</p> | ||||
| 					<img src="assets/image1.png" style="height: 100px;"> | ||||
| 					<pre><code class="hljs" data-trim> | ||||
| 					<pre data-id="code"><code data-line-numbers class="hljs" data-trim> | ||||
| 						function Example() { | ||||
| 						  New line! | ||||
| 						  const [count, setCount] = useState(0); | ||||
| 						} | ||||
| 					</code></pre> | ||||
|  | @ -48,6 +49,36 @@ | |||
| 					<p data-id="text-props" style="background: #555; line-height: 3em; letter-spacing: 0.2em;">Line Height & Letter Spacing</p> | ||||
| 				</section> | ||||
| 
 | ||||
| 				<section data-auto-animate> | ||||
| 					<pre data-id="code"><code data-line-numbers class="hljs" data-trim> | ||||
| 						import React, { useState } from 'react'; | ||||
| 
 | ||||
| 						function Example() { | ||||
| 						  const [count, setCount] = useState(0); | ||||
| 
 | ||||
| 						  return ( | ||||
| 						    ... | ||||
| 						  ); | ||||
| 						} | ||||
| 					</code></pre> | ||||
| 				</section> | ||||
| 				<section data-auto-animate> | ||||
| 					<pre data-id="code"><code data-line-numbers class="hljs" data-trim> | ||||
| 						function Example() { | ||||
| 						  const [count, setCount] = useState(0); | ||||
| 
 | ||||
| 						  return ( | ||||
| 						    <div> | ||||
| 						      <p>You clicked {count} times</p> | ||||
| 						      <button onClick={() => setCount(count + 1)}> | ||||
| 						        Click me | ||||
| 						      </button> | ||||
| 						    </div> | ||||
| 						  ); | ||||
| 						} | ||||
| 					</code></pre> | ||||
| 				</section> | ||||
| 
 | ||||
| 				<section> | ||||
| 					<section data-auto-animate> | ||||
| 						<h3>Swapping list items</h3> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Hakim El Hattab
						Hakim El Hattab