I wanted timed text that's friendly to repeat readers, letting them click through. After some playing around with various designs, I've settled on this <<fade-in>> macro that's similar to <<timed>>, but:
All the content is rendered right away and made invisible, instead of delaying the rendering.
Links in the hidden content are visible as gray (censored) blocks.
The section that will appear next has a blinking "..." indicator.
Skip the delay by clicking or tapping on the "..." or on a censored link.
If something like this seems generally useful, I can create a PR to integrate a version of this into sugarcube. I'd maybe add a few configuration options in the macro, so authors can do some simple variants without fiddling with the CSS. (This would also make it easier to evolve built-in styles without breaking compatibility).
Here it is as separate JS and CSS
fade-in.js
```js
/*
* This description assumes you're using the default style.
* <>
* A blinking "..." appears here.
* After 1.5 seconds, this section's text fades in.
* Click/tap on the "..." will skip the delay.
* Links in this section are shown without delay, in their proper location,
* but blocked out with a gray rectangle.
* Click/tap on any blocked-out link will also skip the delay.
* <>
* After the first section has faded-in, the "..." appears here.
* After another 1s, this section's text fades in.
* There can be any number of "next" sections.
* <>
*/
Macro.add("fade-in", {
tags: ["next"],
handler: function() {
const queue = [];
let timeout = null;
const next = () => {
if (queue.length === 0) return;
const [delay, span] = queue[0];
span.addClass("fade-in-next");
if (timeout != null) clearTimeout(timeout);
timeout = setTimeout(() => {
queue.shift();
span.removeClass("fade-in-hidden fade-in-next");
if (document.contains(span[0])) next();
}, delay);
};
const skipTo = jq => {
const pos = queue.findIndex(q => q[1] === jq);
if (pos < 0) return;
for (let i = 0; i <= pos; i++) {
queue.shift()[1].removeClass("fade-in-hidden fade-in-next");
}
next();
};
this.payload.forEach((section, i) => {
let delay = Util.fromCssTime(section.args[0]);
delay = Math.max(delay, Engine.minDomActionDelay);
const span = $("")
.addClass("fade-in fade-in-hidden")
.wiki(section.contents)
.appendTo(this.output);
const hurry = ev => {
span[0].removeEventListener("click", hurry, true);
if (!span.hasClass("fade-in-hidden")) return;
ev.preventDefault();
ev.stopPropagation();
skipTo(span);
};
span[0].addEventListener("click", hurry, true);
queue.push([delay, span]);
});
setTimeout(next);
}
});
```
fade-in.css
```css
/* for each fade-in section */
.fade-in {
transition: all .4s ease-in;
}
/*
* Hide text. Note: using opacity or filter here will make it impossible
* to have links visible (child filter cannot override parent).
* The :not clause gives this rule a high CSS specificity, making this
* more likely to override complex selectors elsewhere.
*/
.fade-in-hidden,
.fade-in-hidden:not(#give#this#rule#high#specificity) * {
background-image: unset;
color: rgba(0, 0, 0, 0);
pointer-events: none;
}
/* Hide media. */
.fade-in-hidden:not(#give#this#rule#high#specificity) img,
.fade-in-hidden:not(#give#this#rule#high#specificity) video {
opacity: 0;
}
/* Block-out hidden links. */
.fade-in-hidden:not(#give#this#rule#high#specificity) a {
background-color: #222;
border-radius: 4px;
color: rgba(0, 0, 0, 0);
pointer-events: auto;
}
.fade-in-hidden:not(#give#this#rule#high#specificity) a:hover {
background-color: #333;
color: rgba(0, 0, 0, 0);
}
/* Put flashing dots at the section that will be revealed next. */
.fade-in-next:not(#give#this#rule#high#specificity)::before {
animation: 0.5s 1s ease-in-out alternate both infinite fade-in-blink;
color: #fff;
content: ". . .";
cursor: pointer;
display: inline-block;
font-weight: bold;
pointer-events: auto;
position: absolute;
}
@keyframes fade-in-blink {
from { opacity: 0.4; }
to { opacity: 1; }
}
```
And here it is bundled as an init passage (put this in a passage tagged "init" or include it from "StoryInit"):
I wanted timed text that's friendly to repeat readers, letting them click through. After some playing around with various designs, I've settled on this
<<fade-in>>
macro that's similar to<<timed>>
, but:If something like this seems generally useful, I can create a PR to integrate a version of this into sugarcube. I'd maybe add a few configuration options in the macro, so authors can do some simple variants without fiddling with the CSS. (This would also make it easier to evolve built-in styles without breaking compatibility).
Here it is as separate JS and CSS
fade-in.js
```js /* * This description assumes you're using the default style. * <fade-in.css
```css /* for each fade-in section */ .fade-in { transition: all .4s ease-in; } /* * Hide text. Note: using opacity or filter here will make it impossible * to have links visible (child filter cannot override parent). * The :not clause gives this rule a high CSS specificity, making this * more likely to override complex selectors elsewhere. */ .fade-in-hidden, .fade-in-hidden:not(#give#this#rule#high#specificity) * { background-image: unset; color: rgba(0, 0, 0, 0); pointer-events: none; } /* Hide media. */ .fade-in-hidden:not(#give#this#rule#high#specificity) img, .fade-in-hidden:not(#give#this#rule#high#specificity) video { opacity: 0; } /* Block-out hidden links. */ .fade-in-hidden:not(#give#this#rule#high#specificity) a { background-color: #222; border-radius: 4px; color: rgba(0, 0, 0, 0); pointer-events: auto; } .fade-in-hidden:not(#give#this#rule#high#specificity) a:hover { background-color: #333; color: rgba(0, 0, 0, 0); } /* Put flashing dots at the section that will be revealed next. */ .fade-in-next:not(#give#this#rule#high#specificity)::before { animation: 0.5s 1s ease-in-out alternate both infinite fade-in-blink; color: #fff; content: ". . ."; cursor: pointer; display: inline-block; font-weight: bold; pointer-events: auto; position: absolute; } @keyframes fade-in-blink { from { opacity: 0.4; } to { opacity: 1; } } ```And here it is bundled as an init passage (put this in a passage tagged "init" or include it from "StoryInit"):
fade-in.sc2init
```js //<> ```