Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Andrew Seales
Kubernetes Presentation
Commits
d42d88da
Commit
d42d88da
authored
Mar 09, 2020
by
Hakim El Hattab
Browse files
break autoanimate and loading of slide content into separate controllers
parent
ddd13ee9
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
dist/reveal.min.js
View file @
d42d88da
This diff is collapsed.
Click to expand it.
js/controllers/autoanimate.js
0 → 100644
View file @
d42d88da
import
{
extend
,
toArray
,
createStyleSheet
}
from
'
../utils/util.js
'
export
default
class
AutoAnimate
{
constructor
(
Reveal
)
{
this
.
Reveal
=
Reveal
;
// Counter used to generate unique IDs for auto-animated elements
this
.
autoAnimateCounter
=
0
;
}
/**
* Runs an auto-animation between the given slides.
*
* @param {HTMLElement} fromSlide
* @param {HTMLElement} toSlide
*/
run
(
fromSlide
,
toSlide
)
{
// Clean up after prior animations
this
.
reset
();
// Ensure that both slides are auto-animate targets
if
(
fromSlide
.
hasAttribute
(
'
data-auto-animate
'
)
&&
toSlide
.
hasAttribute
(
'
data-auto-animate
'
)
)
{
// Create a new auto-animate sheet
this
.
autoAnimateStyleSheet
=
this
.
autoAnimateStyleSheet
||
createStyleSheet
();
let
animationOptions
=
this
.
getAutoAnimateOptions
(
toSlide
);
// Set our starting state
fromSlide
.
dataset
.
autoAnimate
=
'
pending
'
;
toSlide
.
dataset
.
autoAnimate
=
'
pending
'
;
// Inject our auto-animate styles for this transition
let
css
=
this
.
getAutoAnimatableElements
(
fromSlide
,
toSlide
).
map
(
elements
=>
{
return
this
.
getAutoAnimateCSS
(
elements
.
from
,
elements
.
to
,
elements
.
options
||
{},
animationOptions
,
this
.
autoAnimateCounter
++
);
}
);
// Animate unmatched elements, if enabled
if
(
toSlide
.
dataset
.
autoAnimateUnmatched
!==
'
false
'
&&
this
.
Reveal
.
getConfig
().
autoAnimateUnmatched
===
true
)
{
this
.
getUnmatchedAutoAnimateElements
(
toSlide
).
forEach
(
unmatchedElement
=>
{
unmatchedElement
.
dataset
.
autoAnimateTarget
=
'
unmatched
'
;
}
);
css
.
push
(
`[data-auto-animate="running"] [data-auto-animate-target="unmatched"] { transition: opacity
${
animationOptions
.
duration
*
0.8
}
s ease
${
animationOptions
.
duration
*
0.2
}
s; }`
);
}
// Setting the whole chunk of CSS at once is the most
// efficient way to do this. Using sheet.insertRule
// is multiple factors slower.
this
.
autoAnimateStyleSheet
.
innerHTML
=
css
.
join
(
''
);
// Start the animation next cycle
requestAnimationFrame
(
()
=>
{
if
(
this
.
autoAnimateStyleSheet
)
{
// This forces our newly injected styles to be applied in Firefox
getComputedStyle
(
this
.
autoAnimateStyleSheet
).
fontWeight
;
toSlide
.
dataset
.
autoAnimate
=
'
running
'
;
}
}
);
this
.
Reveal
.
dispatchEvent
(
'
autoanimate
'
,
{
fromSlide
:
fromSlide
,
toSlide
:
toSlide
,
sheet
:
this
.
autoAnimateStyleSheet
}
);
}
}
/**
* Rolls back all changes that we've made to the DOM so
* that as part of animating.
*/
reset
()
{
// Reset slides
toArray
(
this
.
Reveal
.
getRevealElement
().
querySelectorAll
(
'
[data-auto-animate]:not([data-auto-animate=""])
'
)
).
forEach
(
element
=>
{
element
.
dataset
.
autoAnimate
=
''
;
}
);
// Reset elements
toArray
(
this
.
Reveal
.
getRevealElement
().
querySelectorAll
(
'
[data-auto-animate-target]
'
)
).
forEach
(
element
=>
{
delete
element
.
dataset
.
autoAnimateTarget
;
}
);
// Remove the animation sheet
if
(
this
.
autoAnimateStyleSheet
&&
this
.
autoAnimateStyleSheet
.
parentNode
)
{
this
.
autoAnimateStyleSheet
.
parentNode
.
removeChild
(
this
.
autoAnimateStyleSheet
);
this
.
autoAnimateStyleSheet
=
null
;
}
}
/**
* Auto-animates the properties of an element from their original
* values to their new 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
*/
getAutoAnimateCSS
(
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 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
;
}
/**
* Returns the auto-animate options for the given element.
*
* @param {HTMLElement} element Element to pick up options
* from, either a slide or an animation target
* @param {Object} [inheritedOptions] Optional set of existing
* options
*/
getAutoAnimateOptions
(
element
,
inheritedOptions
)
{
let
options
=
{
easing
:
this
.
Reveal
.
getConfig
().
autoAnimateEasing
,
duration
:
this
.
Reveal
.
getConfig
().
autoAnimateDuration
,
delay
:
0
};
options
=
extend
(
options
,
inheritedOptions
);
// Inherit options from parent elements
if
(
element
.
closest
&&
element
.
parentNode
)
{
let
autoAnimatedParent
=
element
.
parentNode
.
closest
(
'
[data-auto-animate-target]
'
);
if
(
autoAnimatedParent
)
{
options
=
this
.
getAutoAnimateOptions
(
autoAnimatedParent
,
options
);
}
}
if
(
element
.
dataset
.
autoAnimateEasing
)
{
options
.
easing
=
element
.
dataset
.
autoAnimateEasing
;
}
if
(
element
.
dataset
.
autoAnimateDuration
)
{
options
.
duration
=
parseFloat
(
element
.
dataset
.
autoAnimateDuration
);
}
if
(
element
.
dataset
.
autoAnimateDelay
)
{
options
.
delay
=
parseFloat
(
element
.
dataset
.
autoAnimateDelay
);
}
return
options
;
}
/**
* Returns an object containing all of the properties
* that can be auto-animated for the given element and
* their current computed values.
*
* @param {String} direction 'from' or 'to'
*/
getAutoAnimatableProperties
(
direction
,
element
,
elementOptions
)
{
let
properties
=
{
styles
:
[]
};
// Position and size
if
(
elementOptions
.
translate
!==
false
||
elementOptions
.
scale
!==
false
)
{
let
bounds
;
// Custom auto-animate may optionally return a custom tailored
// measurement function
if
(
typeof
elementOptions
.
measure
===
'
function
'
)
{
bounds
=
elementOptions
.
measure
(
element
);
}
else
{
bounds
=
element
.
getBoundingClientRect
();
}
properties
.
x
=
bounds
.
x
;
properties
.
y
=
bounds
.
y
;
properties
.
width
=
bounds
.
width
;
properties
.
height
=
bounds
.
height
;
}
const
computedStyles
=
getComputedStyle
(
element
);
// CSS styles
(
elementOptions
.
styles
||
this
.
Reveal
.
getConfig
().
autoAnimateStyles
).
forEach
(
style
=>
{
let
value
;
// `style` is either the property name directly, or an object
// definition of a style property
if
(
typeof
style
===
'
string
'
)
style
=
{
property
:
style
};
if
(
typeof
style
.
from
!==
'
undefined
'
&&
direction
===
'
from
'
)
{
value
=
{
value
:
style
.
from
,
explicitValue
:
true
};
}
else
if
(
typeof
style
.
to
!==
'
undefined
'
&&
direction
===
'
to
'
)
{
value
=
{
value
:
style
.
to
,
explicitValue
:
true
};
}
else
{
value
=
computedStyles
[
style
.
property
];
}
if
(
value
!==
''
)
{
properties
.
styles
[
style
.
property
]
=
value
;
}
}
);
return
properties
;
}
/**
* Get a list of all element pairs that we can animate
* between the given slides.
*
* @param {HTMLElement} fromSlide
* @param {HTMLElement} toSlide
*
* @return {Array} Each value is an array where [0] is
* the element we're animating from and [1] is the
* element we're animating to
*/
getAutoAnimatableElements
(
fromSlide
,
toSlide
)
{
let
matcher
=
typeof
this
.
Reveal
.
getConfig
().
autoAnimateMatcher
===
'
function
'
?
this
.
Reveal
.
getConfig
().
autoAnimateMatcher
:
this
.
getAutoAnimatePairs
;
let
pairs
=
matcher
.
call
(
this
,
fromSlide
,
toSlide
);
let
reserved
=
[];
// Remove duplicate pairs
return
pairs
.
filter
(
(
pair
,
index
)
=>
{
if
(
reserved
.
indexOf
(
pair
.
to
)
===
-
1
)
{
reserved
.
push
(
pair
.
to
);
return
true
;
}
}
);
}
/**
* Identifies matching elements between slides.
*
* You can specify a custom matcher function by using
* the `autoAnimateMatcher` config option.
*/
getAutoAnimatePairs
(
fromSlide
,
toSlide
)
{
let
pairs
=
[];
const
codeNodes
=
'
pre
'
;
const
textNodes
=
'
h1, h2, h3, h4, h5, h6, p, li
'
;
const
mediaNodes
=
'
img, video, iframe
'
;
// Eplicit matches via data-id
this
.
findAutoAnimateMatches
(
pairs
,
fromSlide
,
toSlide
,
'
[data-id]
'
,
node
=>
{
return
node
.
nodeName
+
'
:::
'
+
node
.
getAttribute
(
'
data-id
'
);
}
);
// Text
this
.
findAutoAnimateMatches
(
pairs
,
fromSlide
,
toSlide
,
textNodes
,
node
=>
{
return
node
.
nodeName
+
'
:::
'
+
node
.
innerText
;
}
);
// Media
this
.
findAutoAnimateMatches
(
pairs
,
fromSlide
,
toSlide
,
mediaNodes
,
node
=>
{
return
node
.
nodeName
+
'
:::
'
+
(
node
.
getAttribute
(
'
src
'
)
||
node
.
getAttribute
(
'
data-src
'
)
);
}
);
// Code
this
.
findAutoAnimateMatches
(
pairs
,
fromSlide
,
toSlide
,
codeNodes
,
node
=>
{
return
node
.
nodeName
+
'
:::
'
+
node
.
innerText
;
}
);
pairs
.
forEach
(
pair
=>
{
// Disable scale transformations on text nodes, we transiition
// each individual text property instead
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
this
.
findAutoAnimateMatches
(
pairs
,
pair
.
from
,
pair
.
to
,
'
.hljs .hljs-ln-code
'
,
node
=>
{
return
node
.
textContent
;
},
{
scale
:
false
,
styles
:
[],
measure
:
this
.
getLocalBoundingBox
.
bind
(
this
)
}
);
// Line numbers
this
.
findAutoAnimateMatches
(
pairs
,
pair
.
from
,
pair
.
to
,
'
.hljs .hljs-ln-line[data-line-number]
'
,
node
=>
{
return
node
.
getAttribute
(
'
data-line-number
'
);
},
{
scale
:
false
,
styles
:
[
'
width
'
],
measure
:
this
.
getLocalBoundingBox
.
bind
(
this
)
}
);
}
},
this
);
return
pairs
;
}
/**
* Helper method which returns a bounding box based on
* the given elements offset coordinates.
*
* @param {HTMLElement} element
* @return {Object} x, y, width, height
*/
getLocalBoundingBox
(
element
)
{
const
presentationScale
=
this
.
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
*/
findAutoAnimateMatches
(
pairs
,
fromScope
,
toScope
,
selector
,
serializer
,
animationOptions
)
{
let
fromMatches
=
{};
let
toMatches
=
{};
[].
slice
.
call
(
fromScope
.
querySelectorAll
(
selector
)
).
forEach
(
(
element
,
i
)
=>
{
const
key
=
serializer
(
element
);
if
(
typeof
key
===
'
string
'
&&
key
.
length
)
{
fromMatches
[
key
]
=
fromMatches
[
key
]
||
[];
fromMatches
[
key
].
push
(
element
);
}
}
);
[].
slice
.
call
(
toScope
.
querySelectorAll
(
selector
)
).
forEach
(
(
element
,
i
)
=>
{
const
key
=
serializer
(
element
);
toMatches
[
key
]
=
toMatches
[
key
]
||
[];
toMatches
[
key
].
push
(
element
);
let
fromElement
;
// Retrieve the 'from' element
if
(
fromMatches
[
key
]
)
{
const
pimaryIndex
=
toMatches
[
key
].
length
-
1
;
const
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
* fading of unmatched elements is turned on, these elements
* will fade when going between auto-animate slides.
*
* Note that parents of auto-animate targets are NOT considerd
* unmatched since fading them would break the auto-animation.
*
* @param {HTMLElement} rootElement
* @return {Array}
*/
getUnmatchedAutoAnimateElements
(
rootElement
)
{
return
[].
slice
.
call
(
rootElement
.
children
).
reduce
(
(
result
,
element
)
=>
{
const
containsAnimatedElements
=
element
.
querySelector
(
'
[data-auto-animate-target]
'
);
// The element is unmatched if
// - It is not an auto-animate target
// - It does not contain any auto-animate targets
if
(
!
element
.
hasAttribute
(
'
data-auto-animate-target
'
)
&&
!
containsAnimatedElements
)
{
result
.
push
(
element
);
}
if
(
element
.
querySelector
(
'
[data-auto-animate-target]
'
)
)
{
result
=
result
.
concat
(
this
.
getUnmatchedAutoAnimateElements
(
element
)
);
}
return
result
;
},
[]
);
}
}
\ No newline at end of file
js/controllers/plugins.js
View file @
d42d88da
...
...
@@ -7,8 +7,8 @@ export default class Plugins {
constructor
()
{
// Flags our current state (
pending
-> loading -> loaded)
this
.
state
=
'
pending
'
;
// Flags our current state (
idle
-> loading -> loaded)
this
.
state
=
'
idle
'
;
// An id:instance map of currently registed plugins
this
.
registeredPlugins
=
{};
...
...
js/controllers/slidecontent.js
0 → 100644
View file @
d42d88da
import
{
HORIZONTAL_SLIDES_SELECTOR
,
VERTICAL_SLIDES_SELECTOR
}
from
'
../utils/constants.js
'
import
{
extend
,
toArray
,
closestParent
}
from
'
../utils/util.js
'
export
default
class
SlideContent
{
constructor
(
Reveal
)
{
this
.
Reveal
=
Reveal
;
}
/**
* Called when the given slide is within the configured view
* distance. Shows the slide element and loads any content
* that is set to load lazily (data-src).
*
* @param {HTMLElement} slide Slide to show
*/
load
(
slide
,
options
=
{}
)
{
// Show the slide element
slide
.
style
.
display
=
this
.
Reveal
.
getConfig
().
display
;
// Media elements with data-src attributes
toArray
(
slide
.
querySelectorAll
(
'
img[data-src], video[data-src], audio[data-src], iframe[data-src]
'
)
).
forEach
(
element
=>
{
if
(
element
.
tagName
!==
'
IFRAME
'
||
shouldPreload
(
element
)
)
{
element
.
setAttribute
(
'
src
'
,
element
.
getAttribute
(
'
data-src
'
)
);
element
.
setAttribute
(
'
data-lazy-loaded
'
,
''
);
element
.
removeAttribute
(
'
data-src
'
);
}
}
);
// Media elements with <source> children
toArray
(
slide
.
querySelectorAll
(
'
video, audio
'
)
).
forEach
(
media
=>
{
let
sources
=
0
;
toArray
(
media
.
querySelectorAll
(
'
source[data-src]
'
)
).
forEach
(
source
=>
{
source
.
setAttribute
(
'
src
'
,
source
.
getAttribute
(
'
data-src
'
)
);
source
.
removeAttribute
(
'
data-src
'
);
source
.
setAttribute
(
'
data-lazy-loaded
'
,
''
);
sources
+=
1
;
}
);
// If we rewrote sources for this video/audio element, we need