@braccato/core 1.16.13 → 1.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/engine.js +26 -9
  2. package/package.json +1 -1
package/dist/engine.js CHANGED
@@ -122,7 +122,7 @@ export function createAnimationEngineInstance(engineDocument, engineWindow, host
122
122
  skipScrolls: 0,
123
123
  skipScrollsDecayTimes: [],
124
124
  scrollResumeTime: 0,
125
- scrollPos: 0,
125
+ scrollPos: -1,
126
126
  selectedElementIndex: 0,
127
127
  wasUserScrolling: false,
128
128
  lastScrollElements: [],
@@ -1679,7 +1679,18 @@ function measureTrailingItem(lyricsElement, className) {
1679
1679
  const bounds = getRelativeLayoutBounds(lyricsElement, element);
1680
1680
  return { lyricElement: element, position: bounds.y, height: bounds.height };
1681
1681
  }
1682
- function prepareLineScrollOffsets(engine, lines, activeLineIndex, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config) {
1682
+ // A leading line slower than the one behind closes their gap by distance times progress difference.
1683
+ function lineScrollStaggerScale(lines, activeLineIndex, scrollDistancePx, naturalAdvance) {
1684
+ if (naturalAdvance)
1685
+ return 1;
1686
+ const active = lines[activeLineIndex];
1687
+ const steps = [lines[activeLineIndex - 1], lines[activeLineIndex + 1]]
1688
+ .filter(neighbour => neighbour !== undefined)
1689
+ .map(neighbour => Math.abs(active.position - neighbour.position));
1690
+ const stepPx = steps.length > 0 ? Math.min(...steps) : active.height;
1691
+ return Math.min(1, stepPx / scrollDistancePx);
1692
+ }
1693
+ function prepareLineScrollOffsets(engine, lines, activeLineIndex, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config, naturalAdvance) {
1683
1694
  if (!config.enabled.scroll || activeLineIndex < 0) {
1684
1695
  return null;
1685
1696
  }
@@ -1742,12 +1753,16 @@ function prepareLineScrollOffsets(engine, lines, activeLineIndex, scrollDeltaPx,
1742
1753
  startTranslates = batchResolveLineScrollProperty(engine, items, "translate", item => lineScrollTranslate(item.side, "start", config.lineScroll.differentialEffects), style => normalizedTranslate(style.translate));
1743
1754
  endTranslates = batchResolveLineScrollProperty(engine, items, "translate", item => lineScrollTranslate(item.side, "end", config.lineScroll.differentialEffects), style => normalizedTranslate(style.translate));
1744
1755
  }
1756
+ const staggerScale = lineScrollStaggerScale(lines, activeLineIndex, scrollDistancePx, naturalAdvance);
1757
+ const activeDurationMs = requests.find(request => request.relativeIndex === 0)?.timing?.durationMs;
1745
1758
  return {
1746
1759
  items: requests.map((request, requestIndex) => {
1747
1760
  const timing = request.timing;
1748
1761
  return {
1749
1762
  ...request.item,
1750
- durationMs: timing.durationMs,
1763
+ durationMs: activeDurationMs === undefined
1764
+ ? timing.durationMs
1765
+ : activeDurationMs + (timing.durationMs - activeDurationMs) * staggerScale,
1751
1766
  startEasing: timing.startEasing,
1752
1767
  endEasing: timing.endEasing,
1753
1768
  startTranslate: startTranslates ? startTranslates[requestIndex] : defaultStartTranslate,
@@ -1789,7 +1804,7 @@ function pendingLineScrollMatches(engine, activeLine, fromScrollTop, toScrollTop
1789
1804
  Math.abs(engine.pendingLineScroll.fromScrollTop - fromScrollTop) <= 2 &&
1790
1805
  Math.abs(engine.pendingLineScroll.toScrollTop - toScrollTop) <= 2);
1791
1806
  }
1792
- function commitOrPrepareLineScroll(engine, lines, activeLine, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config) {
1807
+ function commitOrPrepareLineScroll(engine, lines, activeLine, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config, naturalAdvance) {
1793
1808
  if (pendingLineScrollMatches(engine, activeLine, fromScrollTop, toScrollTop)) {
1794
1809
  const pending = engine.pendingLineScroll;
1795
1810
  engine.pendingLineScroll = null;
@@ -1797,16 +1812,16 @@ function commitOrPrepareLineScroll(engine, lines, activeLine, scrollDeltaPx, fro
1797
1812
  return;
1798
1813
  }
1799
1814
  dropPendingLineScroll(engine);
1800
- const plan = prepareLineScrollOffsets(engine, lines, lines.findIndex(line => line.lyricElement === activeLine.lyricElement), scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config);
1815
+ const plan = prepareLineScrollOffsets(engine, lines, lines.findIndex(line => line.lyricElement === activeLine.lyricElement), scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config, naturalAdvance);
1801
1816
  if (plan)
1802
1817
  startPreparedLineScroll(engine, plan);
1803
1818
  }
1804
- function prepareUpcomingLineScroll(engine, lines, activeLine, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config) {
1819
+ function prepareUpcomingLineScroll(engine, lines, activeLine, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config, naturalAdvance) {
1805
1820
  if (pendingLineScrollMatches(engine, activeLine, fromScrollTop, toScrollTop))
1806
1821
  return;
1807
1822
  dropPendingLineScroll(engine);
1808
1823
  const activeLineIndex = lines.findIndex(line => line.lyricElement === activeLine.lyricElement);
1809
- const plan = prepareLineScrollOffsets(engine, lines, activeLineIndex, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config);
1824
+ const plan = prepareLineScrollOffsets(engine, lines, activeLineIndex, scrollDeltaPx, fromScrollTop, toScrollTop, viewportHeight, config, naturalAdvance);
1810
1825
  if (plan) {
1811
1826
  engine.pendingLineScroll = {
1812
1827
  plan,
@@ -2640,6 +2655,8 @@ export function tickView(engine, currentTime, options) {
2640
2655
  }
2641
2656
  }
2642
2657
  const timeUntilUpcomingScrollMs = (lastActiveLyric.time - lyricScrollTime) * 1000;
2658
+ // Only a scroll from where the engine itself left the view keeps the theme's full stagger.
2659
+ const naturalAdvance = !timeJumped && !engine.wasUserScrolling && engine.scrollPos >= 0 && Math.abs(scrollTop - engine.scrollPos) <= 2;
2643
2660
  if (smoothScroll &&
2644
2661
  animationConfig.enabled.scroll &&
2645
2662
  !newLyricSelected &&
@@ -2649,7 +2666,7 @@ export function tickView(engine, currentTime, options) {
2649
2666
  !engine.lastScrollElements.includes(lastActiveLyric) &&
2650
2667
  Math.abs(scrollTop - scrollPos) > 2) {
2651
2668
  updateVisibleLyricWillChange(engine, lines, scrollTop, scrollPos, tabRendererHeight);
2652
- prepareUpcomingLineScroll(engine, getLineScrollItems(engine, lines), lastActiveLyric, scrollPos - scrollTop, scrollTop, scrollPos, tabRendererHeight, animationConfig);
2669
+ prepareUpcomingLineScroll(engine, getLineScrollItems(engine, lines), lastActiveLyric, scrollPos - scrollTop, scrollTop, scrollPos, tabRendererHeight, animationConfig, naturalAdvance);
2653
2670
  }
2654
2671
  if (engine.wasUserScrolling || newLyricSelected) {
2655
2672
  // Remember future lines only when their group is committed, so they cannot scroll
@@ -2664,7 +2681,7 @@ export function tickView(engine, currentTime, options) {
2664
2681
  if (animationConfig.enabled.scroll) {
2665
2682
  updateVisibleLyricWillChange(engine, lines, scrollTop, scrollPos, tabRendererHeight);
2666
2683
  const lineScrollItems = getLineScrollItems(engine, lines);
2667
- commitOrPrepareLineScroll(engine, lineScrollItems, lastActiveLyric, scrollDeltaPx, scrollTop, scrollPos, tabRendererHeight, animationConfig);
2684
+ commitOrPrepareLineScroll(engine, lineScrollItems, lastActiveLyric, scrollDeltaPx, scrollTop, scrollPos, tabRendererHeight, animationConfig, naturalAdvance);
2668
2685
  }
2669
2686
  }
2670
2687
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braccato/core",
3
- "version": "1.16.13",
3
+ "version": "1.16.14",
4
4
  "description": "Synchronized lyrics renderer with word-by-word animations",
5
5
  "type": "module",
6
6
  "license": "MIT",