Skip to content

Kineto v0.12.3

Latest

Choose a tag to compare

@github-actions github-actions released this 27 Sep 05:58
· 62 commits to main since this release
04bc0c2

Kineto v0.12.3

English

  • ScrollTrigger is no longer refreshed while the page scrolls. The core refreshes ScrollTrigger when the document's height settles (src/layoutRefresh.js); it now also waits for scrolling to stop. A refresh re-measures every trigger in one long task (40–70 ms per refresh on WebKit) and puts the scroll position back, which stalls a scroll in progress and ends a touch fling on iOS — and a page whose blocks settle as they near the screen, like the demo since 0.12.2, refreshed about ten times in twelve screens of scrolling. Gate: tests/browser/layout-refresh.mjs (no refresh while scrolling, one once it stops).
  • Demo on phones: tapping a demo no longer makes the page jump. Found by tapping every one of the demo's 519 controls in an emulated iPhone (WebKit, touch), before and after. A tap was taken for the start of a fling, so a height change right after it was left unanchored. Measuring the blocks above the screen at rest used to draw each one for real, which woke every effect inside and held the page for up to half a second just as the reader tapped; it now lays them out without drawing — a few milliseconds each, exact to the pixel. Anchoring keeps the middle of the screen still, and it also watches the grids inside the block being read: a tall block (Loading Indicator holds six) folded its upper grids as they came near and pulled the tapped card up by 1,000 px. A link to a card in a block that was not planned yet (compare sheet, share link) now plans its fold first instead of landing on a card that folds away a moment later. In the frame of a jump, the block the jump went to is left alone together with its parts — a block drawn for the first time lays its parts out too, and anchoring them threw a scrollIntoView target a screen away. Measuring ahead now runs only for a reader who scrolls by touch (a wheel, a key or the scrollbar never coasts), so a desktop no longer spends frames on it.
  • Lazy: replaying a skeleton while the previous run's image is still loading keeps the replayed skeleton. When that image arrived, the previous run carried on with the new run's layers and faded the skeleton out at once, so the replay showed nothing (and a fade scheduled just before a replay removed the new layers). Each run is numbered and an outdated one stops at its next step; replay() cancels the previous run's pending timers and frame. Gate: tests/browser/lifecycle-edges.mjs (Chromium, WebKit, Firefox).
  • Cursor: a cursor on an element with content stays inside that element, however the element looked when Kineto started. The module decided "page-wide or scoped" by the element's size at creation, so a card cursor whose card was hidden at that moment (a closed tab panel, a collapsed accordion, a card not on the page yet) measured 0×0 and became page-wide — a second ring following the pointer everywhere, drawn over the page's own cursor. An element that is not drawn at all is now judged by its content; empty holders, global: true and tiny drawn holders stay page-wide as before (docs/modules/cursor.md). Gate: tests/browser/cursor-scope.mjs (Chromium, WebKit, Firefox).
  • Demo: closing a "모든 variant 비교" sheet no longer leaves effects behind. A tile is created when it nears the screen, and that notice can arrive after the sheet was closed; the tile was then created on a stage no longer on the page, where nothing would ever destroy it. On the Cursor sheet this left a second cursor — the orange Ring + Dot ring — following the pointer across the whole page. Tiles that are no longer on the page are not created, and a closed sheet clears its waiting tiles. Gate: tests/variant-compare.mjs (a late notice after closing creates nothing).
  • Demo: the page cursor is the orange dot and ring (the theme's accent, var(--accent)), one ring only. It was a white dot and a pale ring blended with difference, which drew black on the light theme.
  • Overflow Text Hover Roll works on touch. A phone has no hover, so a tapped Hover Roll link navigated before the roll could be seen. A touch tap on an ordinary same-window link now plays the roll first and replays that one click after rollDuration; mouse hover, keyboard activation, downloads, modifier clicks, links that open another window and clicks another script already cancelled keep their native timing. Table: docs/modules/overflow-text.md. Gate: tests/motion-regressions.mjs (from PR #50).
  • Hover-driven modules decide "can the reader hover?" in one place, utils.canHover() (any attached input can hover with a fine pointer). A touchscreen laptop, or a tablet with a trackpad, reports its primary input as coarse and non-hovering even while a mouse is moving, and has touch points: Cursor turned itself off there, and Tilt and Mouse Parallax waited for a gyroscope a laptop never fires, ignoring the mouse. They now follow the mouse; a finger on such a screen hides the page cursor until the mouse moves again. Phones keep the touch path (Cursor tap effects, gyroscope tilt and parallax, Card Glow press light). disableOnMobile (Tilt, Card Glow) and Vibrate's hover trigger use the same answer, and Mega Menu's existing check moved onto the helper. Policy and per-module table (desktop / touch / keyboard): docs/hover-touch-policy.md. Gate: tests/browser/hover-touch.mjs (desktop, touchscreen laptop and phone profiles; fails on 0.12.2).
  • Counter's delayed work obeys teardown: native Pop wakes every character from one shared animation frame (it asked for one per glyph) and owns its completion timeout, and Clock flip/fade track their delayed callbacks, clear them before a rebuild and cancel them on destroy — a destroyed counter no longer animates restored markup or reports onComplete late. Gate: tests/motion-regressions.mjs (from PR #46).
  • Fullpage transition fallbacks have one owner: a new programmatic navigation cancels the previous settle timeout, transitionend cancels its fallback, and destroy clears the settle timeout and the first section-measure frame, so an older transition can no longer unlock input in the middle of a newer one. Gate: tests/motion-regressions.mjs (from PR #47).
  • Kineto.observe() cleans up only the subtrees that were removed. Any child removal used to re-check every live instance on the page; it now walks the removed element trees, and an element moved elsewhere before the batched flush keeps its instance. Gate: tests/performance-runtime.mjs (from PR #41). The Lightbox CSS compaction (#43, #40) and Loading Indicator preset clean-up (#44) from the same PR stack were already on main.
  • README (all seven languages): installation leads with core + only the modules you use — npm, and native ES modules straight from the CDN (dist/modular/…) — then the full bundle as the quickest start, then React and Vue. What each module adds on top of core is a new generated table, docs/module-cost.md (npm run docs:module-cost, refreshed by release:prepare; core 14.9 KB gzip, median module about 2 KB), and the whole-app report docs/consumer-bundle-size.md is current again. Five localized READMEs and three agent docs still said 53 modules; every "N modules" phrase is now checked against the contract.
  • Tests: drawer-layout waits for the selected tab and a settled sheet height instead of fixed sleeps (it flaked once in a full 2-core lane), and states, slider-scroll-snap and glitch-wave honour KT_CHROME like every other browser suite.
  • Build: Vite 8.3.0 (the built files are byte-identical) and the QA fixtures' React 19.3.0, scheduler 0.28.0, Vue 3.5.43 and Rolldown 1.2.9 (Dependabot #18, #20, #22–#24, #31–#33). jsdom 30 is held back: its CSS serializer leaves background-position longhands behind, which the leak gate reads as a style Scroll Shadows did not restore; TypeScript 7, jQuery 4, Playwright 1.63 and actions/download-artifact 8 need their own compatibility runs.
  • Diagnostics: KT_ENGINE_UNAVAILABLE reports an on-demand engine that did not arrive — GSAP + ScrollTrigger for the scroll modules, Lenis for smooth scroll — with the engine, the loader's reason (blocked, SRI mismatch, timeout, disabled) and the modules that were waiting. The loaders never reject, so modules already degraded, but the reason was thrown away and a page behind a CSP or an ad blocker just got quieter effects. Debug-only like every diagnostic; page text never enters it. Docs: docs/diagnostics-and-deprecation.md, troubleshooting "스크롤 효과가 대체 경로로만 동작함". Gate: tests/engine-loader.mjs.

한국어

  • 페이지가 스크롤되는 동안에는 ScrollTrigger를 새로 고치지 않습니다. 코어는 문서 높이가 안정되면 ScrollTrigger를 새로 고치는데(src/layoutRefresh.js), 이제 스크롤이 멈출 때까지도 기다립니다. 새로 고침은 모든 트리거를 긴 작업 하나로 다시 재고(WebKit에서 한 번에 40~70ms) 스크롤 위치를 되돌려 놓아서, 진행 중인 스크롤을 멈칫하게 하고 iOS의 터치 플링을 끊습니다. 0.12.2부터의 데모처럼 블록이 화면에 가까워질 때 자리 잡는 페이지는 12화면 스크롤하는 동안 약 열 번 새로 고쳤습니다. 게이트: tests/browser/layout-refresh.mjs(스크롤 중에는 없음, 멈춘 뒤 한 번).
  • 휴대폰 데모: 데모를 탭해도 페이지가 튀지 않습니다. iPhone 에뮬레이션(WebKit, 터치)에서 데모 컨트롤 519개를 전부 눌러 보며 찾고 확인했습니다. 탭을 플링의 시작으로 여겨, 탭 직후의 높이 변화를 보정하지 않았습니다. 멈춰 있을 때 화면 위쪽 블록을 재던 작업은 블록을 실제로 그려서 그 안의 효과를 모두 깨웠고, 독자가 탭하는 순간 페이지를 최대 0.5초 붙잡았습니다. 이제 그리지 않고 레이아웃만 합니다 — 블록당 몇 ms, 오차 1px 이내. 앵커링은 화면 가운데를 고정하고, 읽고 있는 블록 안의 그리드도 봅니다: 그리드가 여섯 개인 Loading Indicator 같은 긴 블록은 위쪽 그리드가 가까워지며 접히면서 탭한 카드를 1,000px 끌어올렸습니다. 아직 배치되지 않은 블록의 카드로 가는 링크(비교 시트, 공유 링크)는 도착한 뒤 카드가 접혀 사라지는 대신 먼저 접기를 계획합니다. 점프한 프레임에서는 점프 목표 블록을 그 부분까지 함께 건드리지 않습니다 — 처음 그려지는 블록은 부분도 함께 레이아웃되므로, 그 부분을 보정하면 scrollIntoView 목표가 한 화면 밖으로 밀려났습니다. 미리 재는 작업은 터치로 스크롤하는 독자에게만 합니다(휠·키·스크롤바는 관성이 없음). 데스크톱은 여기에 프레임을 쓰지 않습니다.
  • Lazy: 이전 실행의 이미지가 아직 로딩 중일 때 스켈레톤을 다시 재생해도 새 스켈레톤이 유지됩니다. 그 이미지가 도착하면 이전 실행이 새 실행의 레이어로 마저 진행해 스켈레톤을 바로 사라지게 했고(다시 재생이 아무것도 안 보여 줌), 다시 재생 직전에 예약된 페이드가 새 레이어를 지우기도 했습니다. 실행마다 번호를 두어 낡은 실행은 다음 단계에서 멈추고, replay()는 이전 실행의 대기 중인 타이머와 프레임을 취소합니다. 게이트: tests/browser/lifecycle-edges.mjs(Chromium, WebKit, Firefox).
  • 커서: 내용이 있는 요소에 붙인 커서는 Kineto가 시작될 때 그 요소가 어떤 상태였든 그 요소 안에만 머뭅니다. 모듈은 "페이지 전체인가, 영역인가"를 생성 시점의 요소 크기로 정했기 때문에, 그 순간 카드가 숨겨져 있던 카드 커서(닫힌 탭 패널, 접힌 아코디언, 아직 페이지에 붙지 않은 카드)는 0×0으로 재져 페이지 전체 커서가 되었습니다 — 포인터를 어디든 따라다니며 페이지 커서 위에 겹쳐 그려지는 링 하나가 더 생긴 것입니다. 이제 아예 그려지지 않은 요소는 내용으로 판단합니다. 빈 holder, global: true, 화면에 그려진 아주 작은 holder는 전과 같이 페이지 전체입니다(docs/modules/cursor.md). 게이트: tests/browser/cursor-scope.mjs(Chromium, WebKit, Firefox).
  • 데모: "모든 variant 비교" 시트를 닫아도 효과가 남지 않습니다. 타일은 화면에 가까워질 때 만들어지는데, 그 알림이 시트를 닫은 뒤에 도착할 수 있었습니다. 그러면 타일은 이미 페이지에서 빠진 무대 위에 만들어졌고, 아무도 그것을 destroy 하지 않았습니다. Cursor 시트에서는 이 때문에 두 번째 커서 — 주황색 Ring + Dot 링 — 가 페이지 전체에서 포인터를 따라다녔습니다. 이제 페이지에 없는 타일은 만들지 않고, 닫힌 시트는 기다리던 타일을 비웁니다. 게이트: tests/variant-compare.mjs(닫은 뒤 늦게 온 알림은 아무것도 만들지 않음).
  • 데모: 페이지 커서를 주황 점 + 주황 링 하나로 바꿨습니다(테마 강조색 var(--accent)). 전에는 흰 점과 옅은 링을 difference로 섞어서, 밝은 테마에서는 검게 보였습니다.
  • Overflow Text Hover Roll 이 터치에서도 보입니다. 휴대폰에는 호버가 없어 Hover Roll 링크를 탭하면 롤이 보이기 전에 이동했습니다. 같은 창으로 가는 일반 링크를 터치로 탭하면 먼저 롤을 보여 주고 rollDuration 뒤에 그 클릭을 한 번 다시 실행합니다. 마우스 호버·키보드 활성화·다운로드·수정키 클릭·다른 창으로 여는 링크·다른 스크립트가 이미 취소한 클릭은 원래 타이밍 그대로입니다. 표: docs/modules/overflow-text.md. 게이트: tests/motion-regressions.mjs(PR #50).
  • 호버에 반응하는 모듈이 "지금 호버할 수 있나"를 한곳 utils.canHover()(붙어 있는 입력 중 하나라도 정밀 포인터로 호버 가능)에서 판단합니다. 터치스크린 노트북·트랙패드 달린 태블릿은 마우스를 쓰는 중에도 주 입력을 coarse·호버 불가로, 터치 포인트를 1 이상으로 보고합니다. 그래서 Cursor 가 꺼지고, Tilt·Mouse Parallax 는 노트북에서 오지 않는 자이로스코프를 기다리며 마우스를 무시했습니다. 이제 마우스를 따라가고, 그런 화면을 손가락으로 만지면 페이지 커서가 숨었다가 마우스가 움직이면 돌아옵니다. 휴대폰은 기존 터치 경로(Cursor 탭 효과, 자이로 Tilt·Parallax, Card Glow 누름 빛) 그대로입니다. disableOnMobile(Tilt·Card Glow)과 Vibrate 호버 트리거도 같은 판단을 쓰고, Mega Menu 의 기존 검사도 이 헬퍼로 옮겼습니다. 정책과 모듈별 표(데스크톱/터치/키보드): docs/hover-touch-policy.md. 게이트: tests/browser/hover-touch.mjs(데스크톱·터치스크린 노트북·휴대폰 프로필, 0.12.2 에서 실패).
  • Counter 의 지연 작업이 destroy 를 따릅니다. 네이티브 Pop 은 글자마다 요청하던 애니메이션 프레임을 하나로 합쳐 모든 글자를 시작하고 완료 타이머도 소유하며, Clock flip/fade 는 지연 콜백을 추적해 재구성 전에 정리하고 destroy 에서 취소합니다. destroy 된 카운터가 복원된 마크업을 움직이거나 늦게 onComplete 를 부르지 않습니다. 게이트: tests/motion-regressions.mjs(PR #46).
  • Fullpage 전환의 대체 타이머가 최신 전환 하나에만 속합니다. 새 programmatic 이동은 이전 settle 타이머를 취소하고, transitionend 가 오면 대체 타이머를 지우며, destroy 는 settle 타이머와 첫 섹션 측정 프레임까지 취소합니다. 이전 전환이 새 전환 도중 입력 잠금을 풀지 못합니다. 게이트: tests/motion-regressions.mjs(PR #47).
  • Kineto.observe() 가 실제로 빠진 하위 트리만 정리합니다. 자식 하나가 빠질 때마다 페이지의 모든 인스턴스를 다시 검사하던 것을 빠진 요소 트리만 훑도록 좁혔고, 묶음 처리 전에 다른 곳으로 옮겨진 요소는 인스턴스를 유지합니다. 게이트: tests/performance-runtime.mjs(PR #41). 같은 PR 묶음의 Lightbox CSS 압축(#43, #40)과 Loading Indicator 프리셋 정리(#44)는 이미 main 에 있었습니다.
  • README(7개 언어): 설치 절이 core + 쓰는 모듈만으로 시작합니다 — npm, 그리고 CDN 에서 바로 쓰는 네이티브 ES 모듈(dist/modular/…) — 이어서 가장 빠른 시작인 전체 번들, React·Vue 순입니다. 모듈마다 core 위에 더해지는 크기를 생성 표 docs/module-cost.md(npm run docs:module-cost, release:prepare 가 갱신; core 14.9 KB gzip, 모듈 중앙값 약 2 KB)로 공개하고, 앱 전체 측정 docs/consumer-bundle-size.md 도 현재 값으로 갱신했습니다. 번역 README 5개와 에이전트 문서 3개가 아직 53개 모듈이라고 적고 있어, 이제 모든 "N개 모듈" 문구를 계약과 대조합니다.
  • 테스트: drawer-layout 이 고정 대기 대신 선택된 탭과 높이가 멈춘 시트를 기다립니다(2코어 전체 레인에서 한 번 흔들림). states·slider-scroll-snap·glitch-wave 도 다른 브라우저 테스트처럼 KT_CHROME 을 따릅니다.
  • 빌드: Vite 8.3.0(빌드 결과는 바이트 단위로 동일)과 QA fixture 의 React 19.3.0·scheduler 0.28.0·Vue 3.5.43·Rolldown 1.2.9(Dependabot #18, #20, #22–#24, #31–#33). jsdom 30 은 보류합니다 — CSS 직렬화가 background-position 개별 속성을 남겨 누수 게이트가 Scroll Shadows 가 복원하지 않은 스타일로 읽습니다. TypeScript 7·jQuery 4·Playwright 1.63·actions/download-artifact 8 은 별도 호환성 검증이 필요합니다.
  • 진단: KT_ENGINE_UNAVAILABLE 이 필요할 때 불러오는 엔진(스크롤 모듈의 GSAP + ScrollTrigger, 부드러운 스크롤의 Lenis)이 오지 않았음을 엔진·로더가 밝힌 이유(차단·SRI 불일치·시간 초과·비활성화)·기다리던 모듈과 함께 알립니다. 로더는 실패해도 reject 하지 않아 모듈은 이미 물러났지만 이유는 버려졌고, CSP·광고 차단기 뒤의 페이지는 효과가 조용히 줄어들 뿐이었습니다. 다른 진단처럼 debug 일 때만 나가며 페이지 텍스트는 담지 않습니다. 문서: docs/diagnostics-and-deprecation.md, 문제 해결 "스크롤 효과가 대체 경로로만 동작함". 게이트: tests/engine-loader.mjs.