Example
ドラッグ
import { CenterAlignedPosition, ClockComposer, DefaultSimulatorService, ElementPhysics, ElementRect, GateConditionalVector, Kinetics, PrimaryPointerDownGateClock, PrimaryPointerPositionClock, SpringEngine, WindowResizeTriggerClock,} from "@yyyoichi/chrono-kinesis";
const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const boxEl = document.getElementById("drag-box")!;
// pointerElに対するpointerdownを監視します。const pointerDownGateClock = new PrimaryPointerDownGateClock(boxEl);// gate=1のときのみpointerの位置を追跡します。const pointerPositionClock = new PrimaryPointerPositionClock( document.getElementById("drag-pointer-container")!, { gate: pointerDownGateClock, initPosition: pointerDownGateClock, },);
const boxRect = new ElementRect(boxEl, { trigger: new WindowResizeTriggerClock(),});// ドラッグ中はpointer位置をターゲットとして、ドラッグしていないときは初期位置をターゲットとするベクトルです。const target = new GateConditionalVector( pointerDownGateClock, // ドラッグが開始されればPointerの位置をターゲットとします。 // pointerPositionClockの位置をpointerDomの中心にオフセットするために使用します。 new CenterAlignedPosition(pointerPositionClock, boxRect), // ドラッグが開始されていなければ初期位置をターゲットとします。 boxRect,);simulation.add({ // いずれかがトリガーしている間はアクティブです。 clock: new ClockComposer(pointerDownGateClock, pointerPositionClock), target: target, kinetics: new Kinetics(target.vector(), { engine: new SpringEngine({ settleMs: 800, zeta: 0.7 }), }), physics: new ElementPhysics(boxEl),});simulation.run();<div id="drag-pointer-container" > <div id="drag-box" style="touch-action: none; width: 4rem; height: 4rem; background-color: cadetblue" > </div></div>---export const title = "ドラッグ";type Props = Omit<astroHTML.JSX.HtmlHTMLAttributes, "id">;---
<div id="drag-pointer-container" {...Astro.props}> <div id="drag-box" style="touch-action: none; width: 4rem; height: 4rem; background-color: cadetblue" > </div></div>
<script> import { CenterAlignedPosition, ClockComposer, DefaultSimulatorService, ElementPhysics, ElementRect, GateConditionalVector, Kinetics, PrimaryPointerDownGateClock, PrimaryPointerPositionClock, SpringEngine, WindowResizeTriggerClock, } from "@yyyoichi/chrono-kinesis";
const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const boxEl = document.getElementById("drag-box")!;
// pointerElに対するpointerdownを監視します。 const pointerDownGateClock = new PrimaryPointerDownGateClock(boxEl); // gate=1のときのみpointerの位置を追跡します。 const pointerPositionClock = new PrimaryPointerPositionClock( document.getElementById("drag-pointer-container")!, { gate: pointerDownGateClock, initPosition: pointerDownGateClock, }, );
const boxRect = new ElementRect(boxEl, { trigger: new WindowResizeTriggerClock(), }); // ドラッグ中はpointer位置をターゲットとして、ドラッグしていないときは初期位置をターゲットとするベクトルです。 const target = new GateConditionalVector( pointerDownGateClock, // ドラッグが開始されればPointerの位置をターゲットとします。 // pointerPositionClockの位置をpointerDomの中心にオフセットするために使用します。 new CenterAlignedPosition(pointerPositionClock, boxRect), // ドラッグが開始されていなければ初期位置をターゲットとします。 boxRect, ); simulation.add({ // いずれかがトリガーしている間はアクティブです。 clock: new ClockComposer(pointerDownGateClock, pointerPositionClock), target: target, kinetics: new Kinetics(target.vector(), { engine: new SpringEngine({ settleMs: 800, zeta: 0.7 }), }), physics: new ElementPhysics(boxEl), }); simulation.run();</script>ドラッグ移動
import { CenterAlignedPosition, ClockComposer, DefaultSimulatorService, GateConditionalVector, PositionInRegionGate, PrimaryPointerDownGateClock, PrimaryPointerPositionClock, SpringEngine, TeleportKinetics, ElementRect, WindowResizeTriggerClock, ParentSwitchTrigger, TriggerReducer, ElementPhysics,} from "@yyyoichi/chrono-kinesis";
// 1. ドラッグ開始// 2. ドラッグ中->ボックスをドラッグ座標に追従させる// 3. ポインターがリージョンを跨いだらボックスを移動(appendChild)させる// 4. ドロップ終了->本来のボックスの描画座標(padding-box)に移動させる
const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const containerEl = document.getElementById("drag-to-region-container")!;const regionEl = document.getElementById("drag-to-region-region")!;const boxEl = document.getElementById("drag-to-region-box")!;const windowResizeTriggerClock = new WindowResizeTriggerClock();// ドラッグ先矩形。windowリサイズ時に座標を再計測します。const regionRect = new ElementRect(regionEl, { space: "padding-box", trigger: windowResizeTriggerClock,});// マウスポインターconst pointerDownGateClock = new PrimaryPointerDownGateClock(boxEl);const pointerPositionClock = new PrimaryPointerPositionClock(containerEl, { gate: pointerDownGateClock, initPosition: pointerDownGateClock,});// ポインターの位置がリージョン内にあるかどうかを判定するゲート。const inRegionGate = new PositionInRegionGate( pointerPositionClock, regionRect, regionRect,);// inRegionGateが1のときはregionElを親要素に、0のときはcontainerElを親要素にして座標を取得します。const parentSwitchedTrigger = new ParentSwitchTrigger( boxEl, inRegionGate, regionEl, containerEl, { // マウスがdownしているときにのみ切替を許可します。 switchGate: pointerDownGateClock, },);// 追従する矩形。const boxRect = new ElementRect(boxEl, { space: "padding-box", // ウィンドウリサイズ時と親要素切替時に座標を再計測します。 trigger: new TriggerReducer( "OR", windowResizeTriggerClock, parentSwitchedTrigger, ),});
// ポインターに追従するシミュレーション。simulation.add({ clock: new ClockComposer( pointerDownGateClock, pointerPositionClock, windowResizeTriggerClock, ), target: new GateConditionalVector( pointerDownGateClock, new CenterAlignedPosition(pointerPositionClock, boxRect), boxRect, ), kinetics: new TeleportKinetics(boxRect, { engine: new SpringEngine({ settleMs: 800, zeta: 0.7 }), }), physics: new ElementPhysics(boxEl),});simulation.run();
window.addEventListener("beforeunload", () => { simulation.destroy();});<div > <div id="drag-to-region-container" style="position: relative; display: flex; justify-content: flex-end; width: 100%; height: 100%; touch-action: none;" > <!-- ドラッグされるボックス --> <div id="drag-to-region-box" style="position: absolute; top: 0; left: 0; width: 4rem; height: 4rem; margin: 0; touch-action: none; background-color: cadetblue;" > </div> <!-- ドラッグエリア(リージョン) --> <div style="width: 50%; height: 100%; margin: 0; border: 1rem lightgray dashed;" > <div id="drag-to-region-region" style="position: relative; width: 100%; height: 100%; margin: 0;" > <!-- ここにボックスが配置されます。 --> </div> </div> </div></div>---export const title = "ドラッグ移動";type Props = Omit<astroHTML.JSX.HtmlHTMLAttributes, "id">;---
<div {...Astro.props}> <div id="drag-to-region-container" style="position: relative; display: flex; justify-content: flex-end; width: 100%; height: 100%; touch-action: none;" > <!-- ドラッグされるボックス --> <div id="drag-to-region-box" style="position: absolute; top: 0; left: 0; width: 4rem; height: 4rem; margin: 0; touch-action: none; background-color: cadetblue;" > </div> <!-- ドラッグエリア(リージョン) --> <div style="width: 50%; height: 100%; margin: 0; border: 1rem lightgray dashed;" > <div id="drag-to-region-region" style="position: relative; width: 100%; height: 100%; margin: 0;" > <!-- ここにボックスが配置されます。 --> </div> </div> </div></div>
<script> import { CenterAlignedPosition, ClockComposer, DefaultSimulatorService, GateConditionalVector, PositionInRegionGate, PrimaryPointerDownGateClock, PrimaryPointerPositionClock, SpringEngine, TeleportKinetics, ElementRect, WindowResizeTriggerClock, ParentSwitchTrigger, TriggerReducer, ElementPhysics, } from "@yyyoichi/chrono-kinesis";
// 1. ドラッグ開始 // 2. ドラッグ中->ボックスをドラッグ座標に追従させる // 3. ポインターがリージョンを跨いだらボックスを移動(appendChild)させる // 4. ドロップ終了->本来のボックスの描画座標(padding-box)に移動させる
const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const containerEl = document.getElementById("drag-to-region-container")!; const regionEl = document.getElementById("drag-to-region-region")!; const boxEl = document.getElementById("drag-to-region-box")!; const windowResizeTriggerClock = new WindowResizeTriggerClock(); // ドラッグ先矩形。windowリサイズ時に座標を再計測します。 const regionRect = new ElementRect(regionEl, { space: "padding-box", trigger: windowResizeTriggerClock, }); // マウスポインター const pointerDownGateClock = new PrimaryPointerDownGateClock(boxEl); const pointerPositionClock = new PrimaryPointerPositionClock(containerEl, { gate: pointerDownGateClock, initPosition: pointerDownGateClock, }); // ポインターの位置がリージョン内にあるかどうかを判定するゲート。 const inRegionGate = new PositionInRegionGate( pointerPositionClock, regionRect, regionRect, ); // inRegionGateが1のときはregionElを親要素に、0のときはcontainerElを親要素にして座標を取得します。 const parentSwitchedTrigger = new ParentSwitchTrigger( boxEl, inRegionGate, regionEl, containerEl, { // マウスがdownしているときにのみ切替を許可します。 switchGate: pointerDownGateClock, }, ); // 追従する矩形。 const boxRect = new ElementRect(boxEl, { space: "padding-box", // ウィンドウリサイズ時と親要素切替時に座標を再計測します。 trigger: new TriggerReducer( "OR", windowResizeTriggerClock, parentSwitchedTrigger, ), });
// ポインターに追従するシミュレーション。 simulation.add({ clock: new ClockComposer( pointerDownGateClock, pointerPositionClock, windowResizeTriggerClock, ), target: new GateConditionalVector( pointerDownGateClock, new CenterAlignedPosition(pointerPositionClock, boxRect), boxRect, ), kinetics: new TeleportKinetics(boxRect, { engine: new SpringEngine({ settleMs: 800, zeta: 0.7 }), }), physics: new ElementPhysics(boxEl), }); simulation.run();
window.addEventListener("beforeunload", () => { simulation.destroy(); });</script>マウス追従
import { ClockComposer, DefaultSimulatorService, ElementPhysics, ElementRect, PrimaryPointerPositionClock, TeleportKinetics, WindowResizeTriggerClock,} from "@yyyoichi/chrono-kinesis";const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const windowResizeTriggerClock = new WindowResizeTriggerClock();const boxEl = document.getElementById("mouse-hover-box")!;const boxRect = new ElementRect(boxEl, { // windowリサイズ時に位置を再度取得します。 trigger: windowResizeTriggerClock,});// container内のpointermoveを監視します。const pointerPositionClock = new PrimaryPointerPositionClock( document.getElementById("mouse-hover-container")!, { initPosition: boxRect, },);simulation.add({ clock: new ClockComposer(pointerPositionClock, windowResizeTriggerClock), target: pointerPositionClock, // 初期座標はboxRectです。(初期座標の変更があれば位置を修正します) kinetics: new TeleportKinetics(boxRect), physics: new ElementPhysics(boxEl),});simulation.run();<div id="mouse-hover-container" > <div id="mouse-hover-box" style="width: 1rem; height: 1rem; background-color: cadetblue" > </div></div>---export const title = "マウス追従";type Props = Omit<astroHTML.JSX.HtmlHTMLAttributes, "id">;---
<div id="mouse-hover-container" {...Astro.props}> <div id="mouse-hover-box" style="width: 1rem; height: 1rem; background-color: cadetblue" > </div></div>
<script> import { ClockComposer, DefaultSimulatorService, ElementPhysics, ElementRect, PrimaryPointerPositionClock, TeleportKinetics, WindowResizeTriggerClock, } from "@yyyoichi/chrono-kinesis"; const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const windowResizeTriggerClock = new WindowResizeTriggerClock(); const boxEl = document.getElementById("mouse-hover-box")!; const boxRect = new ElementRect(boxEl, { // windowリサイズ時に位置を再度取得します。 trigger: windowResizeTriggerClock, }); // container内のpointermoveを監視します。 const pointerPositionClock = new PrimaryPointerPositionClock( document.getElementById("mouse-hover-container")!, { initPosition: boxRect, }, ); simulation.add({ clock: new ClockComposer(pointerPositionClock, windowResizeTriggerClock), target: pointerPositionClock, // 初期座標はboxRectです。(初期座標の変更があれば位置を修正します) kinetics: new TeleportKinetics(boxRect), physics: new ElementPhysics(boxEl), }); simulation.run();</script>アニメーションDOM追従
import { ClockComposer, DefaultSimulatorService, ElementPhysics, ElementRect, PrimaryPointerPositionClock, SpringEngine, TeleportKinetics, VectorComposer, WindowResizeTriggerClock, type VectorReadablePort,} from "@yyyoichi/chrono-kinesis";const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const containerEl = document.getElementById("mouse-snake-container")!;const boxEl = document.getElementById("mouse-snake-box")!;
const windowResizeTrigger = new WindowResizeTriggerClock();const boxRect = new ElementRect(boxEl, { trigger: windowResizeTrigger,});const pointerPositionClock = new PrimaryPointerPositionClock(containerEl, { initPosition: boxRect,});const engine = new SpringEngine({ settleMs: 400, zeta: 0.7 });const kinetics = new TeleportKinetics(boxRect, { engine: engine });const clock = new ClockComposer(pointerPositionClock, windowResizeTrigger);
simulation.add({ clock: clock, target: new VectorComposer(pointerPositionClock, boxRect), kinetics: kinetics, physics: new ElementPhysics(boxEl),});
let target: VectorReadablePort = kinetics;for (let i = 1; i <= 5; i++) { const cloneEl = boxEl.cloneNode(true) as HTMLElement; cloneEl.removeAttribute("id"); cloneEl.style.opacity = `${1 - 0.1 * i}`; containerEl.appendChild(cloneEl);
const cloneRect = new ElementRect(cloneEl, { trigger: windowResizeTrigger, }); const cloneKinetics = new TeleportKinetics(cloneRect, { engine: engine.clone({ settleMs: 400 - i * 10 }), }); simulation.add({ clock: clock, target: new VectorComposer(target, cloneRect), kinetics: cloneKinetics, physics: new ElementPhysics(cloneEl), }); target = cloneKinetics;}simulation.run();<div id="mouse-snake-container" > <div id="mouse-snake-box" style="width: 1rem; height: 1rem; background-color: cadetblue; margin-top: 0;" > </div></div>---export const title = "アニメーションDOM追従";type Props = Omit<astroHTML.JSX.HtmlHTMLAttributes, "id">;---
<div id="mouse-snake-container" {...Astro.props}> <div id="mouse-snake-box" style="width: 1rem; height: 1rem; background-color: cadetblue; margin-top: 0;" > </div></div>
<script> import { ClockComposer, DefaultSimulatorService, ElementPhysics, ElementRect, PrimaryPointerPositionClock, SpringEngine, TeleportKinetics, VectorComposer, WindowResizeTriggerClock, type VectorReadablePort, } from "@yyyoichi/chrono-kinesis"; const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const containerEl = document.getElementById("mouse-snake-container")!; const boxEl = document.getElementById("mouse-snake-box")!;
const windowResizeTrigger = new WindowResizeTriggerClock(); const boxRect = new ElementRect(boxEl, { trigger: windowResizeTrigger, }); const pointerPositionClock = new PrimaryPointerPositionClock(containerEl, { initPosition: boxRect, }); const engine = new SpringEngine({ settleMs: 400, zeta: 0.7 }); const kinetics = new TeleportKinetics(boxRect, { engine: engine }); const clock = new ClockComposer(pointerPositionClock, windowResizeTrigger);
simulation.add({ clock: clock, target: new VectorComposer(pointerPositionClock, boxRect), kinetics: kinetics, physics: new ElementPhysics(boxEl), });
let target: VectorReadablePort = kinetics; for (let i = 1; i <= 5; i++) { const cloneEl = boxEl.cloneNode(true) as HTMLElement; cloneEl.removeAttribute("id"); cloneEl.style.opacity = `${1 - 0.1 * i}`; containerEl.appendChild(cloneEl);
const cloneRect = new ElementRect(cloneEl, { trigger: windowResizeTrigger, }); const cloneKinetics = new TeleportKinetics(cloneRect, { engine: engine.clone({ settleMs: 400 - i * 10 }), }); simulation.add({ clock: clock, target: new VectorComposer(target, cloneRect), kinetics: cloneKinetics, physics: new ElementPhysics(cloneEl), }); target = cloneKinetics; } simulation.run();</script>シートダイアログ
import { DefaultSimulatorService, DialogGateClock, DialogSilentClosePhysics, ElementPhysics, ElementRect, GateConditionalVector, Kinetics, OffsetPosition, SpringEngine,} from "@yyyoichi/chrono-kinesis";const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const dialogEl = document.getElementById( "sheet-dialog",)! as HTMLDialogElement;const dialogGateClock = new DialogGateClock(dialogEl, { type: "modal", closedby: "any",});const dialogRect = new ElementRect(dialogEl);
simulation.add({ clock: dialogGateClock, target: new GateConditionalVector( dialogGateClock, new OffsetPosition(dialogRect, -300), dialogRect, ), kinetics: new Kinetics(dialogRect.position(), { engine: new SpringEngine({ settleMs: 400, zeta: 0.7 }), }), physics: [ new ElementPhysics(dialogEl), new DialogSilentClosePhysics(dialogGateClock, { stopThreshold: 80, }), ],});
// add Event
const openEvent = () => dialogGateClock.open();// 直接close()しない点に注意。requestClose()は状態のみCloseする。const closeEvent = () => dialogGateClock.requestClose();const openButton = document.getElementById("sheet-dialog-open-button")!;const closeButton = document.getElementById("sheet-dialog-close-button")!;openButton.addEventListener("click", openEvent);closeButton.addEventListener("click", closeEvent);simulation.run();
window.addEventListener("beforeunload", () => { openButton.removeEventListener("click", openEvent); closeButton.removeEventListener("click", closeEvent); simulation.destroy();});<div > <button type="button" id="sheet-dialog-open-button">open</button> <dialog id="sheet-dialog"> <button type="button" id="sheet-dialog-close-button">close</button> </dialog></div>
<style> #sheet-dialog { top: 0; left: 100vw; width: 300px; height: 100dvh; max-height: 100dvh; margin: 0; padding: 0; border: none; overflow: hidden; background-color: cadetblue; } #sheet-dialog::backdrop { backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }</style>---export const title = "シートダイアログ";type Props = Omit<astroHTML.JSX.HtmlHTMLAttributes, "id">;---
<div {...Astro.props}> <button type="button" id="sheet-dialog-open-button">open</button> <dialog id="sheet-dialog"> <button type="button" id="sheet-dialog-close-button">close</button> </dialog></div>
<style> #sheet-dialog { top: 0; left: 100vw; width: 300px; height: 100dvh; max-height: 100dvh; margin: 0; padding: 0; border: none; overflow: hidden; background-color: cadetblue; } #sheet-dialog::backdrop { backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }</style>
<script> import { DefaultSimulatorService, DialogGateClock, DialogSilentClosePhysics, ElementPhysics, ElementRect, GateConditionalVector, Kinetics, OffsetPosition, SpringEngine, } from "@yyyoichi/chrono-kinesis"; const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });
const dialogEl = document.getElementById( "sheet-dialog", )! as HTMLDialogElement; const dialogGateClock = new DialogGateClock(dialogEl, { type: "modal", closedby: "any", }); const dialogRect = new ElementRect(dialogEl);
simulation.add({ clock: dialogGateClock, target: new GateConditionalVector( dialogGateClock, new OffsetPosition(dialogRect, -300), dialogRect, ), kinetics: new Kinetics(dialogRect.position(), { engine: new SpringEngine({ settleMs: 400, zeta: 0.7 }), }), physics: [ new ElementPhysics(dialogEl), new DialogSilentClosePhysics(dialogGateClock, { stopThreshold: 80, }), ], });
// add Event
const openEvent = () => dialogGateClock.open(); // 直接close()しない点に注意。requestClose()は状態のみCloseする。 const closeEvent = () => dialogGateClock.requestClose(); const openButton = document.getElementById("sheet-dialog-open-button")!; const closeButton = document.getElementById("sheet-dialog-close-button")!; openButton.addEventListener("click", openEvent); closeButton.addEventListener("click", closeEvent); simulation.run();
window.addEventListener("beforeunload", () => { openButton.removeEventListener("click", openEvent); closeButton.removeEventListener("click", closeEvent); simulation.destroy(); });</script>スクロールイベント
Scroll ME!
画面の下部50%にボックスが入ると表示されます。
import { DefaultSimulatorService, DomVisualizer, ElementResizeTriggerClock, LinearScaledVector, Kinetics, ViewportSignal, ViewportTriggerClock, TriggerGateReducer, ClockComposer, ElementPhysics,} from "@yyyoichi/chrono-kinesis";const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 });// 要素が順スクロールで50%に達したらOpacityを1にして、逆スクロールで10%に達したら0にする例です。
const rootEl = document.getElementById("viewport-root")!;const boxEl = document.getElementById("viewport-target-box")!;
// 順スクロールで50%に到達したらトリガーします。const onClock = new ViewportTriggerClock(boxEl, { threshold: 0.5, triggerDirection: "forward", viewportSignal: new ViewportSignal({ root: rootEl }), resizeTrigger: new ElementResizeTriggerClock(rootEl),});// 逆スクロールで10%に到達したらトリガーします。const offClock = onClock.newClock({ threshold: 0.1, triggerDirection: "backward",});
const opacity = new LinearScaledVector( // onClockがトリガーされたら1、offClockがトリガーされたら0にします new TriggerGateReducer(onClock, offClock), // 値を100-1000の範囲にスケーリングします { min: 100, max: 1000 },);const vis = new DomVisualizer() .noTranslate() .opacity((st) => `${Math.round(st.absolute[0]) / 1000}`);simulation.add({ clock: new ClockComposer(onClock, offClock), target: opacity, kinetics: new Kinetics([0]), physics: new ElementPhysics(boxEl, vis),});simulation.run();<div id="viewport-root" {...props} style={style + "overflow: auto;"}> <div style="height: 500px; width:100%"> <span style="position: sticky; top: 0; background-color: var(--sl-color-bg); color: var(--sl-color-text); padding: 0.5rem; font-weight: bold;" > Scroll ME! </span> <div style="margin-top: 130px; display: block; text-align: center;"> 画面の下部50%にボックスが入ると表示されます。 </div> <div id="viewport-target-box" style="opacity: 0.1; height: 200px; width:50%; margin: 0 auto; background-color: cadetblue;" > </div> </div></div>---export const title = "スクロールイベント";type Props = Omit<astroHTML.JSX.HtmlHTMLAttributes, "id">;const { style, ...props } = Astro.props;---
<div id="viewport-root" {...props} style={style + "overflow: auto;"}> <div style="height: 500px; width:100%"> <span style="position: sticky; top: 0; background-color: var(--sl-color-bg); color: var(--sl-color-text); padding: 0.5rem; font-weight: bold;" > Scroll ME! </span> <div style="margin-top: 130px; display: block; text-align: center;"> 画面の下部50%にボックスが入ると表示されます。 </div> <div id="viewport-target-box" style="opacity: 0.1; height: 200px; width:50%; margin: 0 auto; background-color: cadetblue;" > </div> </div></div>
<script> import { DefaultSimulatorService, DomVisualizer, ElementResizeTriggerClock, LinearScaledVector, Kinetics, ViewportSignal, ViewportTriggerClock, TriggerGateReducer, ClockComposer, ElementPhysics, } from "@yyyoichi/chrono-kinesis"; const simulation = new DefaultSimulatorService({ fixedStepSec: 1 / 20 }); // 要素が順スクロールで50%に達したらOpacityを1にして、逆スクロールで10%に達したら0にする例です。
const rootEl = document.getElementById("viewport-root")!; const boxEl = document.getElementById("viewport-target-box")!;
// 順スクロールで50%に到達したらトリガーします。 const onClock = new ViewportTriggerClock(boxEl, { threshold: 0.5, triggerDirection: "forward", viewportSignal: new ViewportSignal({ root: rootEl }), resizeTrigger: new ElementResizeTriggerClock(rootEl), }); // 逆スクロールで10%に到達したらトリガーします。 const offClock = onClock.newClock({ threshold: 0.1, triggerDirection: "backward", });
const opacity = new LinearScaledVector( // onClockがトリガーされたら1、offClockがトリガーされたら0にします new TriggerGateReducer(onClock, offClock), // 値を100-1000の範囲にスケーリングします { min: 100, max: 1000 }, ); const vis = new DomVisualizer() .noTranslate() .opacity((st) => `${Math.round(st.absolute[0]) / 1000}`); simulation.add({ clock: new ClockComposer(onClock, offClock), target: opacity, kinetics: new Kinetics([0]), physics: new ElementPhysics(boxEl, vis), }); simulation.run();</script>