Animation: eigener Bereich unter dem Canvas, getrennt vom Bildexport
Jedes Projekt hat jetzt unter dem Canvas einen Bereich "Animation", im selben Fenster. Er zeigt jede Animation als echtes Blatt in Vorschau, mit Zeitregler, Abspielen und "MP4 exportieren". Eine Animation ist keine Slide. Sie steht in ANIMS und nicht in SLIDES, liegt also nicht im Streifen, taucht in der Slide-Auswahl nicht auf und wird vom Bildexport nicht angefasst. Im Carousel ist sie ein eigenes Element, und ob sie mitkommt, entscheidet sich beim Posten. Ein erster Anlauf machte sie zur sechsten Slide; damit war sie in der Zaehlung und im ZIP mit drin. Der Export laeuft ueber den neuen Endpunkt POST /anim in serve.py. Er startet animation.mjs in einem eigenen Faden, die Seite fragt den Stand ueber GET /anim?id= ab und zeigt Fortschritt und Pfad. Ein Browser kann kein MP4 schreiben, und eine offene Anfrage ueber zwei Minuten geht nicht -- zumal sich animation.mjs waehrenddessen selbst Seiten von diesem Server holt. Aufgenommen wird im neuen Modus ?nur=anim&k=<key>: dort zeigt die Seite allein das Blatt der Animation, in 1080 x 1350 an 0,0. Damit entfaellt jede Ausschnittsrechnung und mit ihr die Falle, dass ein zu schmales Fenster ein leeres Bild liefert. Als erstes Beispiel post6-animChat, fuenf Sekunden: die Antwort wird Zeichen fuer Zeichen getippt, abgeschickt, waechst als Blase in den Verlauf, die Aufgabenkarte klappt auf, die Systemzeile bestaetigt, alles blendet zurueck auf den Anfang. Getippt wird VERLAUF[3].text, also die Nachricht, die im Werkzeug an dieser Stelle ohnehin folgt. H.264 High, yuv420p, 1080 x 1350, 30 fps, stumme AAC-Spur, +faststart: das nimmt Instagram fuer ein animiertes Element im Carousel an. GIF nicht, das landet als Standbild. Ein Video im Carousel wird auch kein Reel, das passiert nur bei einem einzeln geposteten Video. Nebenbei: - post4, post5 und post6 trugen im Panel noch den Kopf ihrer Vorlage. - video/README.md und video/bauen.mjs behaupteten, ffmpeg sei hier nicht installiert. Stimmt seit 9.0.1 nicht mehr; die Bildsequenz bleibt dort trotzdem, mit Begruendung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ce87a7fb57
commit
c7c05a85cc
14 files changed
+3295
-247
No files matched your search
+363
-2
@@ -7,6 +7,11 @@
|
||||
Bedienoberflaeche kurz auf. Deshalb hier oben und nicht im Hauptskript. */
|
||||
if (location.search.indexOf("nur=streifen") >= 0)
|
||||
document.documentElement.dataset.nur = "streifen";
|
||||
/* Rendermodus fuer den Videobau: die Seite zeigt dann allein das Blatt der
|
||||
Animation, in natuerlicher Groesse an 0,0. animation.mjs stellt das Fenster
|
||||
auf 1080 x 1350 und nimmt es ohne Ausschnittsrechnung auf. */
|
||||
if (location.search.indexOf("nur=anim") >= 0)
|
||||
document.documentElement.dataset.nur = "anim";
|
||||
</script>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -450,6 +455,89 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
.karte-name span { font-size: 27px; font-weight: 700; letter-spacing: -.03em; line-height: 1.15; }
|
||||
.karte-name small { font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; }
|
||||
|
||||
/* Die Buehne war bisher eine einzige Flaeche, auf der Scroller und
|
||||
Fussleiste uebereinander lagen. Jetzt sind es zwei Zeilen: oben die
|
||||
Flaeche mit dem Streifen, darunter die Animation -- im selben Fenster,
|
||||
unter dem Canvas. Das Umhaengen macht animLeisteBauen(), so bleibt der
|
||||
HTML-Rumpf jeder Datei unveraendert. */
|
||||
.stage { display: flex; flex-direction: column; }
|
||||
#stageflaeche { position: relative; flex: 1 1 auto; min-height: 0; }
|
||||
html[data-nur="streifen"] .stage { display: block; }
|
||||
html[data-nur="streifen"] #stageflaeche { position: static; }
|
||||
|
||||
/* Farben wie der Rest der Oberflaeche: Panelgrund #17181F, Trennlinien
|
||||
#24262F, Bedienfelder #1D1F26 mit Rand #2E313B. Hier standen einmal
|
||||
Variablennamen, die es in dieser Datei nie gab -- eine ungueltige var()
|
||||
ohne Ersatzwert laesst die ganze Angabe fallen, deshalb fehlten Rahmen
|
||||
und Grund, ohne dass irgendwo ein Fehler auftauchte. */
|
||||
#animleiste {
|
||||
flex: 0 0 auto; border-top: 1px solid #24262F;
|
||||
background: #14161C; padding: 13px 20px 16px;
|
||||
max-height: 42vh; overflow-y: auto;
|
||||
}
|
||||
.animkopf { display: flex; align-items: baseline; gap: 10px; margin-bottom: 11px; }
|
||||
.animkopf b {
|
||||
font-size: 11px; font-weight: 700; letter-spacing: .09em;
|
||||
text-transform: uppercase; color: #E8EAEE;
|
||||
}
|
||||
.animkopf span { font-size: 11.5px; color: #6E7480; }
|
||||
.animreihe { display: flex; gap: 14px; flex-wrap: wrap; }
|
||||
.animkarte {
|
||||
display: flex; gap: 14px; align-items: stretch;
|
||||
border: 1px solid #2E313B; border-radius: 10px;
|
||||
padding: 12px; background: #1D1F26;
|
||||
}
|
||||
|
||||
/* Die Vorschau ist das echte Blatt, nur kleiner gestellt. Kein Nachbau:
|
||||
was hier laeuft, ist Zeichen fuer Zeichen dasselbe, was animation.mjs
|
||||
aufnimmt. --animskala bestimmt, wie gross. */
|
||||
.animbuehne {
|
||||
--animskala: .215;
|
||||
flex: none; position: relative; overflow: hidden; border-radius: 6px;
|
||||
width: calc(1080px * var(--animskala));
|
||||
height: calc(1350px * var(--animskala));
|
||||
box-shadow: 0 0 0 1px #2E313B;
|
||||
}
|
||||
.animbuehne > .slide {
|
||||
position: absolute; left: 0; top: 0;
|
||||
transform: scale(var(--animskala)); transform-origin: top left;
|
||||
}
|
||||
|
||||
.animrechts {
|
||||
display: flex; flex-direction: column; gap: 7px;
|
||||
min-width: 268px; max-width: 340px; align-self: start;
|
||||
}
|
||||
.animtitel { font-size: 12.5px; font-weight: 700; color: #E8EAEE; }
|
||||
.animwerte { font-size: 11.5px; color: #6E7480; }
|
||||
.animschieber { width: 100%; accent-color: #F1B729; margin: 3px 0 1px; }
|
||||
.animknoepfe { display: flex; align-items: center; gap: 8px; }
|
||||
.animknoepfe button {
|
||||
font-family: inherit; font-size: 12px; font-weight: 700; cursor: pointer;
|
||||
background: #262933; color: #C7CCD6; border: 1px solid #383C48;
|
||||
border-radius: 7px; padding: 6px 13px;
|
||||
}
|
||||
.animknoepfe button:hover:not(:disabled) { background: #30343F; color: #E8EAEE; }
|
||||
.animknoepfe button:disabled { opacity: .55; cursor: default; }
|
||||
.animzeile {
|
||||
font-size: 10.5px; line-height: 1.5; color: #6E7480; word-break: break-all;
|
||||
}
|
||||
.animzeile[data-ton="ok"] { color: #7BC48A; }
|
||||
.animzeile[data-ton="err"] { color: #E5806E; }
|
||||
.animzeile code, .animhin code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
}
|
||||
.animhin { font-size: 11.5px; line-height: 1.55; color: #6E7480; max-width: 68ch; }
|
||||
.animhin code { font-size: 11px; color: #C7CCD6; }
|
||||
|
||||
/* ─── Rendermodus fuer den Videobau ──────────────────────────────────────
|
||||
Die Seite zeigt allein das Blatt der Animation, an 0,0, in natuerlicher
|
||||
Groesse. animation.mjs stellt das Fenster auf 1080 x 1350 und nimmt es
|
||||
ohne Ausschnittsrechnung auf. */
|
||||
html[data-nur="anim"], html[data-nur="anim"] body {
|
||||
height: auto; min-height: 0; overflow: hidden;
|
||||
background: #fff; display: block; padding: 0; margin: 0;
|
||||
}
|
||||
|
||||
/* ─── Bearbeitung im Canvas ───────────────────────────────────────────────
|
||||
Verschieben läuft über --ex/--ey, also über transform statt über den
|
||||
Textfluss: So verschiebt ein Element nie seine Nachbarn und lässt sich
|
||||
@@ -866,6 +954,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
html[data-nur="streifen"] .menubar,
|
||||
html[data-nur="streifen"] .panel,
|
||||
html[data-nur="streifen"] .hud,
|
||||
html[data-nur="streifen"] #animleiste,
|
||||
html[data-nur="streifen"] .guides { display: none !important; }
|
||||
/* Der Auswahlrahmen ist eine Bedienhilfe und hat in der Uebersicht nichts
|
||||
verloren -- dort ist keine Slide "die aktuelle". */
|
||||
@@ -2297,6 +2386,40 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
'</svg>';
|
||||
}
|
||||
|
||||
/* ═══ Animationen ════════════════════════════════════════════════════════
|
||||
Sie stehen mit Absicht nicht in SLIDES. Eine Animation ist kein Standbild
|
||||
im Streifen: sie wird einzeln gebaut, einzeln exportiert und geht als
|
||||
eigenes Element ins Carousel -- oder eben nicht mit, wenn nur die Bilder
|
||||
gepostet werden. Der Bildexport laesst sie deshalb unberuehrt.
|
||||
|
||||
Aufbau sonst gleich wie eine Slide: dieselben Felder, dieselbe
|
||||
Zeichenfunktion aus VIS, dasselbe Blatt in 1080 x 1350. Der Unterschied
|
||||
ist `dauer` und der zweite Wert t, den die Zeichenfunktion bekommt. */
|
||||
const ANIMS = [];
|
||||
|
||||
/* ═══ Zeitachse ══════════════════════════════════════════════════════════
|
||||
Eine Slide mit `dauer` ist animiert. Ihre Zeichenfunktion bekommt dann
|
||||
einen zweiten Wert: den Fortschritt t zwischen 0 und 1. Sonst aendert
|
||||
sich nichts -- dieselbe Ueberschrift, derselbe Punktgrund, dieselbe
|
||||
Schutzzone. Ein animiertes Bild ist hier eine Slide, die sich bewegt,
|
||||
und kein zweites Format.
|
||||
|
||||
ANIM_T haelt je Slide den aktuellen Stand. Das Bauskript setzt ihn ueber
|
||||
window.animSetzen() und schiesst dann ein Bild ab; die Vorschau in der
|
||||
Seite laesst ihn ueber requestAnimationFrame laufen. Beide gehen durch
|
||||
dieselbe Tuer, damit die Vorschau zeigt, was hinterher herauskommt. */
|
||||
const ANIM_T = {};
|
||||
const animT = j => ANIM_T[j] || 0;
|
||||
|
||||
/* Ein paar Verlaeufe. Linear sieht nach Maschine aus, deshalb sind Ein-
|
||||
und Ausgang gedaempft. `stufe` blendet einen Abschnitt zwischen zwei
|
||||
Zeitpunkten ein und liefert 0 davor und 1 danach. */
|
||||
const daempfen = x => x <= 0 ? 0 : x >= 1 ? 1 : x < .5
|
||||
? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
|
||||
const rueck = x => x <= 0 ? 0 : x >= 1 ? 1
|
||||
: 1 + 2.7 * Math.pow(x - 1, 3) + 1.7 * Math.pow(x - 1, 2);
|
||||
const stufe = (t, von, bis) => t <= von ? 0 : t >= bis ? 1 : (t - von) / (bis - von);
|
||||
|
||||
const VIS = {
|
||||
karte: j =>
|
||||
'<div class="karte-halter">' + visKarte(layoutState[j]) +
|
||||
@@ -2953,7 +3076,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
v.style.cssText =
|
||||
"--vis-w:" + (sl.visW ?? 900) + "px;--vis-left:" + (sl.visLeft ?? 92) +
|
||||
"px;--vis-bottom:" + (sl.visBottom ?? 96) + "px";
|
||||
v.innerHTML = VIS[sl.vis](j);
|
||||
v.innerHTML = VIS[sl.vis](j, animT(j));
|
||||
page.appendChild(v);
|
||||
}
|
||||
|
||||
@@ -3022,6 +3145,242 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
return page;
|
||||
}
|
||||
|
||||
/* ═══ Animation: Steuerung ════════════════════════════════════════════════
|
||||
Eine Tuer fuer beide Wege. Das Bauskript setzt den Fortschritt und
|
||||
schiesst ab, die Vorschau in der Seite laesst ihn laufen -- was hier
|
||||
durchgeht, ist hinterher im Video zu sehen.
|
||||
|
||||
Neu gezeichnet wird nur die Zeichenflaeche, nicht das ganze Blatt: bei
|
||||
150 Bildern macht das den Unterschied zwischen Minuten und einer
|
||||
Viertelstunde. */
|
||||
|
||||
/* Das Blatt einer Animation. Es entsteht wie eine Slide, nur ohne Streifen,
|
||||
ohne Nachbarn und ohne Bearbeiten -- eine Animation wird nicht im Canvas
|
||||
geschoben, sie ist ein eigenes Stueck. Hintergrund, Variante und Muster
|
||||
kommen von der ersten Slide, damit sie im Carousel dazugehoert. */
|
||||
function animSlideBauen(i) {
|
||||
const a = ANIMS[i];
|
||||
const el = document.createElement("div");
|
||||
el.className = "slide";
|
||||
el.dataset.animi = String(i);
|
||||
el.dataset.variant = variant;
|
||||
el.dataset.tilt = "gerade";
|
||||
el.dataset.bg = (bgState[0] || {}).bg || "formen";
|
||||
el.dataset.int = (bgState[0] || {}).int || "dezent";
|
||||
el.dataset.layout = "standard";
|
||||
el.dataset.tagout = tagOut;
|
||||
el.dataset.raster = rasterAn;
|
||||
el.dataset.mitte = textMitte;
|
||||
el.dataset.frame = frameStyle;
|
||||
el.dataset.editmode = "aus";
|
||||
el.style.setProperty("--slide-i", 0);
|
||||
el.innerHTML = bgHtml();
|
||||
|
||||
const sheet = document.createElement("div");
|
||||
sheet.className = "sheet";
|
||||
sheet.style.setProperty("--sheet-w", "1080px");
|
||||
|
||||
const page = document.createElement("div");
|
||||
page.className = "page";
|
||||
page.dataset.layout = "standard";
|
||||
page.style.left = "0px";
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.className = "content";
|
||||
content.innerHTML = contentHtml(a, 0);
|
||||
page.appendChild(content);
|
||||
|
||||
const v = document.createElement("div");
|
||||
v.className = "vis vis-" + a.vis;
|
||||
v.style.cssText =
|
||||
"--vis-w:" + (a.visW ?? 900) + "px;--vis-left:" + (a.visLeft ?? 92) +
|
||||
"px;--vis-bottom:" + (a.visBottom ?? 96) + "px";
|
||||
v.innerHTML = VIS[a.vis](i, animT(i));
|
||||
page.appendChild(v);
|
||||
|
||||
sheet.appendChild(page);
|
||||
el.appendChild(sheet);
|
||||
return el;
|
||||
}
|
||||
|
||||
function animZeichnen(i) {
|
||||
const a = ANIMS[i];
|
||||
if (!a || !VIS[a.vis]) return;
|
||||
document.querySelectorAll('.slide[data-animi="' + i + '"] .vis')
|
||||
.forEach(v => { v.innerHTML = VIS[a.vis](i, animT(i)); });
|
||||
}
|
||||
|
||||
window.ANIM_LISTE = () => ANIMS.map((a, i) =>
|
||||
({ i, key: a.key, dauer: a.dauer || 0, titel: a.eyebrow || a.key }));
|
||||
|
||||
window.animSetzen = (i, t) => {
|
||||
ANIM_T[i] = Math.max(0, Math.min(1, t));
|
||||
animZeichnen(i);
|
||||
const s = document.querySelector('input[data-animschieber="' + i + '"]');
|
||||
if (s && document.activeElement !== s) s.value = String(Math.round(ANIM_T[i] * 1000));
|
||||
return true;
|
||||
};
|
||||
|
||||
/* Die Vorschau laeuft in derselben Geschwindigkeit wie das spaetere Video
|
||||
und wiederholt sich, damit man sieht, ob der Stoss am Ende sitzt -- im
|
||||
Carousel laeuft so ein Element in Schleife. */
|
||||
let animLaeuft = null;
|
||||
function animAbspielen(i, knopf) {
|
||||
if (animLaeuft) {
|
||||
cancelAnimationFrame(animLaeuft.rahmen);
|
||||
const alt = animLaeuft.knopf;
|
||||
animLaeuft = null;
|
||||
if (alt) alt.textContent = "Abspielen";
|
||||
if (alt === knopf) return;
|
||||
}
|
||||
const dauer = (ANIMS[i].dauer || 4) * 1000;
|
||||
const start = performance.now() - ANIM_T[i] * dauer;
|
||||
knopf.textContent = "Anhalten";
|
||||
const schritt = jetzt => {
|
||||
window.animSetzen(i, ((jetzt - start) % dauer) / dauer);
|
||||
animLaeuft = { rahmen: requestAnimationFrame(schritt), knopf };
|
||||
};
|
||||
animLaeuft = { rahmen: requestAnimationFrame(schritt), knopf };
|
||||
}
|
||||
|
||||
/* Der Export. Ein Browser kann kein MP4 schreiben, also stoesst die Seite
|
||||
den Bau auf dem Server an und fragt danach den Stand ab. Bewusst getrennt
|
||||
vom Bildexport: die Animation ist ein eigenes Element im Carousel und
|
||||
soll auch einzeln herauskommen. */
|
||||
async function animExport(i, knopf, zeile) {
|
||||
const a = ANIMS[i];
|
||||
knopf.disabled = true;
|
||||
const alt = knopf.textContent;
|
||||
knopf.textContent = "baut …";
|
||||
zeile.textContent = "Chrome startet …";
|
||||
zeile.dataset.ton = "";
|
||||
try {
|
||||
const an = await fetch("/anim", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ projekt: PROJECT, key: a.key }),
|
||||
});
|
||||
if (!an.ok) throw new Error("HTTP " + an.status + " — läuft serve.py?");
|
||||
const { id } = await an.json();
|
||||
for (;;) {
|
||||
await new Promise(r => setTimeout(r, 1200));
|
||||
const st = await (await fetch("/anim?id=" + encodeURIComponent(id))).json();
|
||||
if (st.zeile) zeile.textContent = st.zeile;
|
||||
if (st.laeuft) continue;
|
||||
if (st.fehler) throw new Error(st.fehler);
|
||||
zeile.textContent = st.pfad + " · " + st.groesse;
|
||||
zeile.dataset.ton = "ok";
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
zeile.textContent = String(e.message || e);
|
||||
zeile.dataset.ton = "err";
|
||||
} finally {
|
||||
knopf.disabled = false;
|
||||
knopf.textContent = alt;
|
||||
}
|
||||
}
|
||||
|
||||
/* Der Bereich unter dem Canvas. Er zeigt jede Animation als eigenes Blatt
|
||||
in Vorschau, nicht als Eintrag in einer Liste: was hier laeuft, ist
|
||||
dasselbe Blatt, das hinterher im Video steht. */
|
||||
function animLeisteBauen() {
|
||||
const liste = window.ANIM_LISTE();
|
||||
let leiste = document.getElementById("animleiste");
|
||||
if (!leiste) {
|
||||
/* Scroller und Fussleiste liegen absolut in der Buehne. Ein Geschwister
|
||||
danach landete deshalb oben im Fluss und lag ueber dem Streifen. Also
|
||||
wandern beide in eine eigene Flaeche, und die Leiste kommt als zweite
|
||||
Zeile darunter -- im selben Fenster, unter dem Canvas. */
|
||||
const buehne = document.getElementById("stage");
|
||||
if (buehne && !document.getElementById("stageflaeche")) {
|
||||
const flaeche = document.createElement("div");
|
||||
flaeche.id = "stageflaeche";
|
||||
while (buehne.firstChild) flaeche.appendChild(buehne.firstChild);
|
||||
buehne.appendChild(flaeche);
|
||||
}
|
||||
leiste = document.createElement("section");
|
||||
leiste.id = "animleiste";
|
||||
(buehne || document.body).appendChild(leiste);
|
||||
}
|
||||
|
||||
const kopf = document.createElement("div");
|
||||
kopf.className = "animkopf";
|
||||
kopf.innerHTML = "<b>Animation</b><span>" +
|
||||
(liste.length
|
||||
? liste.length + (liste.length === 1 ? " Element" : " Elemente") +
|
||||
" · 1080 × 1350 · 30 fps · MP4, getrennt vom Bildexport"
|
||||
: "noch keine in diesem Projekt") + "</span>";
|
||||
leiste.innerHTML = "";
|
||||
leiste.appendChild(kopf);
|
||||
|
||||
if (!liste.length) {
|
||||
leiste.insertAdjacentHTML("beforeend",
|
||||
'<p class="animhin">Eine Animation entsteht als Eintrag in <code>ANIMS</code>: ' +
|
||||
'dieselben Felder wie eine Slide, dazu <code>dauer</code> in Sekunden. Ihre ' +
|
||||
'Zeichenfunktion bekommt einen zweiten Wert <code>t</code> zwischen 0 und 1 und ' +
|
||||
'zeichnet daraus den Stand. Sie liegt bewusst neben <code>SLIDES</code> und ' +
|
||||
'nicht darin: der Bildexport lässt sie in Ruhe.</p>');
|
||||
return;
|
||||
}
|
||||
|
||||
const reihe = document.createElement("div");
|
||||
reihe.className = "animreihe";
|
||||
liste.forEach(a => {
|
||||
const karte = document.createElement("article");
|
||||
karte.className = "animkarte";
|
||||
|
||||
const buehne = document.createElement("div");
|
||||
buehne.className = "animbuehne";
|
||||
buehne.appendChild(animSlideBauen(a.i));
|
||||
karte.appendChild(buehne);
|
||||
|
||||
const rechts = document.createElement("div");
|
||||
rechts.className = "animrechts";
|
||||
rechts.innerHTML =
|
||||
'<div class="animtitel">' + a.titel + '</div>' +
|
||||
'<div class="animwerte">' + a.dauer.toFixed(1) + " s · " +
|
||||
Math.round(a.dauer * 30) + " Bilder · 1080 × 1350</div>" +
|
||||
'<input class="animschieber" type="range" min="0" max="1000" value="0" ' +
|
||||
'data-animschieber="' + a.i + '" aria-label="Zeitpunkt">' +
|
||||
'<div class="animknoepfe">' +
|
||||
'<button type="button" class="animspiel">Abspielen</button>' +
|
||||
'<button type="button" class="animexport">MP4 exportieren</button>' +
|
||||
"</div>" +
|
||||
'<p class="animzeile"><code>node animation.mjs ' + PROJECT + ".html " +
|
||||
a.key + "</code></p>";
|
||||
karte.appendChild(rechts);
|
||||
reihe.appendChild(karte);
|
||||
|
||||
const zeile = rechts.querySelector(".animzeile");
|
||||
rechts.querySelector(".animspiel")
|
||||
.addEventListener("click", e => animAbspielen(a.i, e.currentTarget));
|
||||
rechts.querySelector(".animexport")
|
||||
.addEventListener("click", e => animExport(a.i, e.currentTarget, zeile));
|
||||
rechts.querySelector(".animschieber").addEventListener("input", e => {
|
||||
if (animLaeuft) {
|
||||
cancelAnimationFrame(animLaeuft.rahmen);
|
||||
if (animLaeuft.knopf) animLaeuft.knopf.textContent = "Abspielen";
|
||||
animLaeuft = null;
|
||||
}
|
||||
window.animSetzen(a.i, +e.currentTarget.value / 1000);
|
||||
});
|
||||
});
|
||||
leiste.appendChild(reihe);
|
||||
}
|
||||
|
||||
/* Der Rendermodus fuer animation.mjs: alles weg bis auf das eine Blatt.
|
||||
Aufgebaut wird es erst hier, nach dem Laden von Schrift und Marken --
|
||||
sonst misst das Bauskript eine halbfertige Seite. */
|
||||
function animNurModus() {
|
||||
if (document.documentElement.dataset.nur !== "anim") return;
|
||||
const key = new URLSearchParams(location.search).get("k");
|
||||
const i = ANIMS.findIndex(a => a.key === key);
|
||||
document.body.innerHTML = "";
|
||||
if (i < 0) return;
|
||||
document.body.appendChild(animSlideBauen(i));
|
||||
}
|
||||
|
||||
function renderPage(page, j) {
|
||||
page.querySelector(".content").innerHTML = contentHtml(SLIDES[j], j);
|
||||
if (SLIDES[j].type === "title") page.classList.add("title");
|
||||
@@ -3033,7 +3392,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
dadurch wirkungslos geblieben. Der Zufallskeim ist gesetzt, das erneute
|
||||
Zeichnen liefert also denselben Strich. */
|
||||
const vv = page.querySelector(".vis");
|
||||
if (vv && SLIDES[j].vis && VIS[SLIDES[j].vis]) vv.innerHTML = VIS[SLIDES[j].vis](j);
|
||||
if (vv && SLIDES[j].vis && VIS[SLIDES[j].vis]) vv.innerHTML = VIS[SLIDES[j].vis](j, animT(j));
|
||||
tagEditable(page);
|
||||
applyEdits(page, j);
|
||||
}
|
||||
@@ -4854,6 +5213,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
press("views", "w", view);
|
||||
anzahlNachziehen();
|
||||
show(current);
|
||||
animLeisteBauen();
|
||||
if (verloren.length)
|
||||
setStatus("Fehlende Bilder entfernt: " + verloren.join(", ") +
|
||||
" · bitte neu importieren", "err");
|
||||
@@ -4863,6 +5223,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
["dl-png", "dl-all"].forEach(id => document.getElementById(id).disabled = false);
|
||||
if (checkCollisions()) setStatus("Bereit. " + SLIDES.length +
|
||||
(SLIDES.length === 1 ? " Slide" : " Slides") + ", 1080 × 1350 px", "ok");
|
||||
animNurModus();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus("Fehler beim Laden: " + err.message, "err");
|
||||
|
||||
Reference in new issue
Block a user