refactored and improved auto-animate matcher, supports line-by-line code animations
This commit is contained in:
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
Block a user