Fragments & auto-animate
The mechanism in revealjs that triggers something on the slides instead of advancing to the next slide
.fragmentIt goes on anything: a block, a list item, a span, an empty div.
my-deck.qmd
Each one costs a click, in the order they appear.
.incremental is a convenience. It hands a .fragment to every item in the list, and nothing more.
my-deck.qmd
data-fragment-index says when, independent of where the element sits on the slide.
my-deck.qmd
we will start with the CSS way first
Style each state and you have a new fragment. No JavaScript needed.
styles.scss
The order matters: .visible and .current-fragment are applied at the same moment, so the cascade decides the winner.
Reveal hides a fragment until it fires:
Set both to unset when the element should be on screen the whole time and the fragment only changes it.
styles.scss
No .current-fragment rule, so the highlight arrives and stays.
styles.scss
#{$name} builds the class name, so four colors in a map become four fragment classes.
styles.scss
$colors: ("orange": #FFB81A, "yellow": #FFD571, "brown": #E2AE86, "pink": #FED7E1);
@each $name, $color in $colors {
.reveal .slides section .fragment.hl-#{$name} {
opacity: unset;
visibility: unset;
&.visible {
background-color: lighten($color, 5%);
}
&.current-fragment {
background-color: $color;
}
}
}
color |
the text itself |
background |
the fill behind it |
opacity |
fade something back without removing it |
border |
an underline, a box, a bracket |
transform |
scale, rotate, nudge |
filter |
blur, grayscale, brightness |
Every one of these animates on the compositor, so reveal can tween them for free.
You can change anything, but width, height, margin, and font-size move the rest of the slide with them. That reflow reads as jitter.
transform: scale() grows a thing without touching its neighbours. Reach for that instead.
Color, background, and opacity are the safe playground.
Reveal marked the element with a class, and your stylesheet reacted. Nothing ran, and going backwards undid itself.
JavaScript has no states. It has two events, and undoing the change is your job.
_fragments.html
fragmenthidden should be the exact inverse of fragmentshownIf a change cannot be undone, it makes a bad fragment.
And _fragments.html is a <script> tag with your Reveal.on() calls inside.
A fragment does not have to contain what it changes.
event.fragment.dataset.color reads that value, so one class covers many slides.
Reveal cannot scroll for you, but a fragment can.
_scroll.html
fragmenthidden is the same handler with top: 0.
_scroll.html
Two numbers apart. That is what “the exact inverse” buys you.
Every embedded deck in this workshop is stepped by an empty fragment.
advance-embedded.html
[]{.fragment .advance-slide} in the source, one per step of the inner deck.
Many of the extensions in the next module are this same trick, packaged.
Nothing in them you could not have written here. They just save you the writing.
Eight empty fragments, one animation module, and a ggplot2 call comes apart layer by layer.
Mark two adjacent slides with auto-animate=true and reveal tweens the difference.
data-id and it is notmy-deck.qmd
This used to be the reason nobody did it: every panel is four numbers, and you found them by rendering, squinting, and editing again.
With AI agents this work is a little easier.
data-id divs and their coordinates.incremental list.highlight-last and the SCSS for it, so the current bullet stands outauto-animate=true, and change one thingdata-id and watch the animation get steadierBack to the workshop materials.
Slidecrafting Workshop