Smooth LeeSori dance pose transitions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "LeeSoriDance",
|
||||
"status": "leesori_solo3_pose_sequence_no_overlap_2026_07_04",
|
||||
"status": "leesori_solo3_motion_tween_2026_07_04",
|
||||
"renderMode": "poseSequence",
|
||||
"canvas": {
|
||||
"width": 1024,
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"imageBase": "./Images/",
|
||||
"poseBase": "./Gestures/dance/solo3/",
|
||||
"note": "Solo Dance 3 inspired reusable pose-sequence gesture. Runtime shows exactly one normalized frame at a time to avoid residual image overlap; original parts are retained under partsRig for future rigging.",
|
||||
"note": "Solo Dance 3 inspired reusable pose-sequence gesture. Runtime crossfades normalized frames with per-frame transform offsets so the motion reads as a continuous upper-body dance while still avoiding residual image overlap.",
|
||||
"gestures": [
|
||||
{
|
||||
"id": "leesori.dance.solo3",
|
||||
@@ -21,7 +21,7 @@
|
||||
"idle",
|
||||
"success"
|
||||
],
|
||||
"frameMs": 260,
|
||||
"frameMs": 340,
|
||||
"loop": true,
|
||||
"tags": [
|
||||
"upper-body",
|
||||
@@ -33,29 +33,222 @@
|
||||
"frames": [
|
||||
{
|
||||
"image": "frame_01_ready.png",
|
||||
"label": "ready bounce"
|
||||
"label": "ready bounce",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"rotate": -0.6,
|
||||
"scale": 1
|
||||
},
|
||||
"enter": {
|
||||
"x": -5,
|
||||
"y": 5,
|
||||
"rotate": -0.8,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": 4,
|
||||
"y": -3,
|
||||
"rotate": 0.8,
|
||||
"scale": 1.006
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_03_wrist_wave.png",
|
||||
"label": "wrist wave prep",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -4,
|
||||
"y": -2,
|
||||
"rotate": 0.4,
|
||||
"scale": 1.012
|
||||
},
|
||||
"enter": {
|
||||
"x": -10,
|
||||
"y": 4,
|
||||
"rotate": -0.7,
|
||||
"scale": 0.99
|
||||
},
|
||||
"exit": {
|
||||
"x": 5,
|
||||
"y": -3,
|
||||
"rotate": 0.7,
|
||||
"scale": 1.004
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_02_hand_forward.png",
|
||||
"label": "hand forward"
|
||||
"label": "hand forward",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -8,
|
||||
"y": -5,
|
||||
"rotate": 0.8,
|
||||
"scale": 1.026
|
||||
},
|
||||
"enter": {
|
||||
"x": -12,
|
||||
"y": 5,
|
||||
"rotate": -0.8,
|
||||
"scale": 0.988
|
||||
},
|
||||
"exit": {
|
||||
"x": 8,
|
||||
"y": -4,
|
||||
"rotate": 0.9,
|
||||
"scale": 0.996
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_03_wrist_wave.png",
|
||||
"label": "wrist wave"
|
||||
"label": "wrist wave return",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 3,
|
||||
"y": -2,
|
||||
"rotate": -0.3,
|
||||
"scale": 1.014
|
||||
},
|
||||
"enter": {
|
||||
"x": 8,
|
||||
"y": 4,
|
||||
"rotate": 0.9,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": -5,
|
||||
"y": -4,
|
||||
"rotate": -0.8,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_04_arms_up.png",
|
||||
"label": "arms up"
|
||||
"label": "arms up lift",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 0,
|
||||
"y": -12,
|
||||
"rotate": 0.2,
|
||||
"scale": 1.022
|
||||
},
|
||||
"enter": {
|
||||
"x": -5,
|
||||
"y": 7,
|
||||
"rotate": -0.7,
|
||||
"scale": 0.99
|
||||
},
|
||||
"exit": {
|
||||
"x": 5,
|
||||
"y": -5,
|
||||
"rotate": 0.6,
|
||||
"scale": 1.004
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_05_hair_touch.png",
|
||||
"label": "hair touch"
|
||||
"label": "hair touch",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -5,
|
||||
"y": -4,
|
||||
"rotate": 1,
|
||||
"scale": 1.018
|
||||
},
|
||||
"enter": {
|
||||
"x": 6,
|
||||
"y": 5,
|
||||
"rotate": -0.6,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": -6,
|
||||
"y": -3,
|
||||
"rotate": -0.8,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_04_arms_up.png",
|
||||
"label": "arms up release",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 4,
|
||||
"y": -8,
|
||||
"rotate": -0.4,
|
||||
"scale": 1.018
|
||||
},
|
||||
"enter": {
|
||||
"x": -7,
|
||||
"y": 4,
|
||||
"rotate": 0.8,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": 6,
|
||||
"y": -3,
|
||||
"rotate": 0.7,
|
||||
"scale": 1.004
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_03_wrist_wave.png",
|
||||
"label": "return wave"
|
||||
"label": "settle wave",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -2,
|
||||
"y": -1,
|
||||
"rotate": 0.2,
|
||||
"scale": 1.01
|
||||
},
|
||||
"enter": {
|
||||
"x": 7,
|
||||
"y": 5,
|
||||
"rotate": -0.7,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": -4,
|
||||
"y": -3,
|
||||
"rotate": 0.7,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_01_ready.png",
|
||||
"label": "ready settle",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"rotate": 0.3,
|
||||
"scale": 1.004
|
||||
},
|
||||
"enter": {
|
||||
"x": 6,
|
||||
"y": 4,
|
||||
"rotate": 0.6,
|
||||
"scale": 0.994
|
||||
},
|
||||
"exit": {
|
||||
"x": -5,
|
||||
"y": -3,
|
||||
"rotate": -0.6,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"transitionMs": 190
|
||||
}
|
||||
],
|
||||
"partsRig": {
|
||||
|
||||
@@ -82,19 +82,85 @@ function selectGesture(rig, state) {
|
||||
|| null;
|
||||
}
|
||||
|
||||
function motionTransform(frame, phase) {
|
||||
const motion = frame?.motion || {};
|
||||
const target = motion.target || { x: 0, y: 0, rotate: 0, scale: 1 };
|
||||
const offset = phase === "target"
|
||||
? { x: 0, y: 0, rotate: 0, scale: 1 }
|
||||
: (motion[phase] || { x: 0, y: 0, rotate: 0, scale: 1 });
|
||||
const x = (target.x || 0) + (offset.x || 0);
|
||||
const y = (target.y || 0) + (offset.y || 0);
|
||||
const rotate = (target.rotate || 0) + (offset.rotate || 0);
|
||||
const scale = (target.scale || 1) * (offset.scale || 1);
|
||||
return `translate(${x}px, ${y}px) rotate(${rotate}deg) scale(${scale})`;
|
||||
}
|
||||
|
||||
function frameForElement(element) {
|
||||
const frames = loadedRig?.activeGesture?.frames || [];
|
||||
return frames[Number(element.dataset.frameIndex)] || {};
|
||||
}
|
||||
|
||||
function hideInactiveFrame(frame) {
|
||||
frame.classList.remove("is-active", "is-entering", "is-exiting");
|
||||
frame.style.opacity = "0";
|
||||
frame.style.zIndex = "1";
|
||||
}
|
||||
|
||||
function setActiveGestureFrame(index) {
|
||||
const frames = [...puppet.querySelectorAll(".puppet-frame")];
|
||||
if (!frames.length) return;
|
||||
|
||||
const activeIndex = ((index % frames.length) + frames.length) % frames.length;
|
||||
const transitionMs = Math.max(80, Number(loadedRig?.activeGesture?.transitionMs || 170));
|
||||
const current = frames.find(frame => frame.classList.contains("is-active"));
|
||||
const next = frames[activeIndex];
|
||||
if (!next || current === next) return;
|
||||
|
||||
for (const frame of frames) {
|
||||
frame.classList.toggle("is-active", Number(frame.dataset.frameIndex) === activeIndex);
|
||||
if (frame !== current && frame !== next) {
|
||||
hideInactiveFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
current.classList.remove("is-active", "is-entering");
|
||||
current.classList.add("is-exiting");
|
||||
current.style.transitionDuration = `${transitionMs}ms`;
|
||||
current.style.zIndex = "2";
|
||||
current.style.opacity = "0";
|
||||
current.style.transform = motionTransform(frameForElement(current), "exit");
|
||||
window.setTimeout(() => {
|
||||
if (current.classList.contains("is-exiting")) hideInactiveFrame(current);
|
||||
}, transitionMs + 40);
|
||||
}
|
||||
|
||||
next.classList.remove("is-exiting");
|
||||
next.classList.add("is-entering");
|
||||
next.style.transitionDuration = `${transitionMs}ms`;
|
||||
next.style.zIndex = "3";
|
||||
next.style.opacity = "0";
|
||||
next.style.transform = motionTransform(frameForElement(next), "enter");
|
||||
void next.offsetWidth;
|
||||
next.classList.add("is-active");
|
||||
window.requestAnimationFrame(() => {
|
||||
next.style.opacity = "1";
|
||||
next.style.transform = motionTransform(frameForElement(next), "target");
|
||||
});
|
||||
}
|
||||
|
||||
function startPoseSequence(gesture) {
|
||||
stopGestureTimer();
|
||||
setActiveGestureFrame(0);
|
||||
const frameMs = Math.max(80, Number(gesture.frameMs || 260));
|
||||
loadedRig.activeGesture = gesture;
|
||||
const first = puppet.querySelector('.puppet-frame[data-frame-index="0"]');
|
||||
if (first) {
|
||||
first.style.transitionDuration = "0ms";
|
||||
first.style.opacity = "1";
|
||||
first.style.zIndex = "3";
|
||||
first.style.transform = motionTransform(frameForElement(first), "target");
|
||||
first.classList.add("is-active");
|
||||
}
|
||||
|
||||
const frameMs = Math.max(120, Number(gesture.frameMs || 340));
|
||||
if (gesture.loop !== false && (gesture.frames?.length || 0) > 1) {
|
||||
gestureTimer = window.setInterval(() => {
|
||||
gestureFrame += 1;
|
||||
@@ -123,6 +189,7 @@ function renderPoseSequence(rig, active, gesture) {
|
||||
img.src = `${poseBase}${frame.image}?v=${assetVersion}`;
|
||||
img.alt = "";
|
||||
img.decoding = "async";
|
||||
img.style.transform = motionTransform(frame, "enter");
|
||||
fragment.appendChild(img);
|
||||
}
|
||||
puppet.replaceChildren(fragment);
|
||||
@@ -256,3 +323,4 @@ window.addEventListener("DOMContentLoaded", async () => {
|
||||
setState("idle");
|
||||
post("hostReady");
|
||||
});
|
||||
|
||||
|
||||
@@ -76,13 +76,20 @@ body {
|
||||
.puppet-frame {
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
transform-origin: 50% 38%;
|
||||
transform-origin: 50% 40%;
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 180ms;
|
||||
transition-timing-function: cubic-bezier(.22,.84,.32,1);
|
||||
}
|
||||
|
||||
.puppet-frame.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.puppet-frame.is-exiting {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#puppet[data-mode="pose-sequence"] .puppet-part {
|
||||
display: none;
|
||||
}
|
||||
@@ -456,3 +463,4 @@ body {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+203
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "LeeSoriDance",
|
||||
"status": "leesori_solo3_pose_sequence_no_overlap_2026_07_04",
|
||||
"status": "leesori_solo3_motion_tween_2026_07_04",
|
||||
"renderMode": "poseSequence",
|
||||
"canvas": {
|
||||
"width": 1024,
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"imageBase": "./Images/",
|
||||
"poseBase": "./Gestures/dance/solo3/",
|
||||
"note": "Solo Dance 3 inspired reusable pose-sequence gesture. Runtime shows exactly one normalized frame at a time to avoid residual image overlap; original parts are retained under partsRig for future rigging.",
|
||||
"note": "Solo Dance 3 inspired reusable pose-sequence gesture. Runtime crossfades normalized frames with per-frame transform offsets so the motion reads as a continuous upper-body dance while still avoiding residual image overlap.",
|
||||
"gestures": [
|
||||
{
|
||||
"id": "leesori.dance.solo3",
|
||||
@@ -21,7 +21,7 @@
|
||||
"idle",
|
||||
"success"
|
||||
],
|
||||
"frameMs": 260,
|
||||
"frameMs": 340,
|
||||
"loop": true,
|
||||
"tags": [
|
||||
"upper-body",
|
||||
@@ -33,29 +33,222 @@
|
||||
"frames": [
|
||||
{
|
||||
"image": "frame_01_ready.png",
|
||||
"label": "ready bounce"
|
||||
"label": "ready bounce",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"rotate": -0.6,
|
||||
"scale": 1
|
||||
},
|
||||
"enter": {
|
||||
"x": -5,
|
||||
"y": 5,
|
||||
"rotate": -0.8,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": 4,
|
||||
"y": -3,
|
||||
"rotate": 0.8,
|
||||
"scale": 1.006
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_03_wrist_wave.png",
|
||||
"label": "wrist wave prep",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -4,
|
||||
"y": -2,
|
||||
"rotate": 0.4,
|
||||
"scale": 1.012
|
||||
},
|
||||
"enter": {
|
||||
"x": -10,
|
||||
"y": 4,
|
||||
"rotate": -0.7,
|
||||
"scale": 0.99
|
||||
},
|
||||
"exit": {
|
||||
"x": 5,
|
||||
"y": -3,
|
||||
"rotate": 0.7,
|
||||
"scale": 1.004
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_02_hand_forward.png",
|
||||
"label": "hand forward"
|
||||
"label": "hand forward",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -8,
|
||||
"y": -5,
|
||||
"rotate": 0.8,
|
||||
"scale": 1.026
|
||||
},
|
||||
"enter": {
|
||||
"x": -12,
|
||||
"y": 5,
|
||||
"rotate": -0.8,
|
||||
"scale": 0.988
|
||||
},
|
||||
"exit": {
|
||||
"x": 8,
|
||||
"y": -4,
|
||||
"rotate": 0.9,
|
||||
"scale": 0.996
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_03_wrist_wave.png",
|
||||
"label": "wrist wave"
|
||||
"label": "wrist wave return",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 3,
|
||||
"y": -2,
|
||||
"rotate": -0.3,
|
||||
"scale": 1.014
|
||||
},
|
||||
"enter": {
|
||||
"x": 8,
|
||||
"y": 4,
|
||||
"rotate": 0.9,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": -5,
|
||||
"y": -4,
|
||||
"rotate": -0.8,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_04_arms_up.png",
|
||||
"label": "arms up"
|
||||
"label": "arms up lift",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 0,
|
||||
"y": -12,
|
||||
"rotate": 0.2,
|
||||
"scale": 1.022
|
||||
},
|
||||
"enter": {
|
||||
"x": -5,
|
||||
"y": 7,
|
||||
"rotate": -0.7,
|
||||
"scale": 0.99
|
||||
},
|
||||
"exit": {
|
||||
"x": 5,
|
||||
"y": -5,
|
||||
"rotate": 0.6,
|
||||
"scale": 1.004
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_05_hair_touch.png",
|
||||
"label": "hair touch"
|
||||
"label": "hair touch",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -5,
|
||||
"y": -4,
|
||||
"rotate": 1,
|
||||
"scale": 1.018
|
||||
},
|
||||
"enter": {
|
||||
"x": 6,
|
||||
"y": 5,
|
||||
"rotate": -0.6,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": -6,
|
||||
"y": -3,
|
||||
"rotate": -0.8,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_04_arms_up.png",
|
||||
"label": "arms up release",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 4,
|
||||
"y": -8,
|
||||
"rotate": -0.4,
|
||||
"scale": 1.018
|
||||
},
|
||||
"enter": {
|
||||
"x": -7,
|
||||
"y": 4,
|
||||
"rotate": 0.8,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": 6,
|
||||
"y": -3,
|
||||
"rotate": 0.7,
|
||||
"scale": 1.004
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_03_wrist_wave.png",
|
||||
"label": "return wave"
|
||||
"label": "settle wave",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": -2,
|
||||
"y": -1,
|
||||
"rotate": 0.2,
|
||||
"scale": 1.01
|
||||
},
|
||||
"enter": {
|
||||
"x": 7,
|
||||
"y": 5,
|
||||
"rotate": -0.7,
|
||||
"scale": 0.992
|
||||
},
|
||||
"exit": {
|
||||
"x": -4,
|
||||
"y": -3,
|
||||
"rotate": 0.7,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"image": "frame_01_ready.png",
|
||||
"label": "ready settle",
|
||||
"motion": {
|
||||
"target": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"rotate": 0.3,
|
||||
"scale": 1.004
|
||||
},
|
||||
"enter": {
|
||||
"x": 6,
|
||||
"y": 4,
|
||||
"rotate": 0.6,
|
||||
"scale": 0.994
|
||||
},
|
||||
"exit": {
|
||||
"x": -5,
|
||||
"y": -3,
|
||||
"rotate": -0.6,
|
||||
"scale": 1.002
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"transitionMs": 190
|
||||
}
|
||||
],
|
||||
"partsRig": {
|
||||
|
||||
@@ -82,19 +82,85 @@ function selectGesture(rig, state) {
|
||||
|| null;
|
||||
}
|
||||
|
||||
function motionTransform(frame, phase) {
|
||||
const motion = frame?.motion || {};
|
||||
const target = motion.target || { x: 0, y: 0, rotate: 0, scale: 1 };
|
||||
const offset = phase === "target"
|
||||
? { x: 0, y: 0, rotate: 0, scale: 1 }
|
||||
: (motion[phase] || { x: 0, y: 0, rotate: 0, scale: 1 });
|
||||
const x = (target.x || 0) + (offset.x || 0);
|
||||
const y = (target.y || 0) + (offset.y || 0);
|
||||
const rotate = (target.rotate || 0) + (offset.rotate || 0);
|
||||
const scale = (target.scale || 1) * (offset.scale || 1);
|
||||
return `translate(${x}px, ${y}px) rotate(${rotate}deg) scale(${scale})`;
|
||||
}
|
||||
|
||||
function frameForElement(element) {
|
||||
const frames = loadedRig?.activeGesture?.frames || [];
|
||||
return frames[Number(element.dataset.frameIndex)] || {};
|
||||
}
|
||||
|
||||
function hideInactiveFrame(frame) {
|
||||
frame.classList.remove("is-active", "is-entering", "is-exiting");
|
||||
frame.style.opacity = "0";
|
||||
frame.style.zIndex = "1";
|
||||
}
|
||||
|
||||
function setActiveGestureFrame(index) {
|
||||
const frames = [...puppet.querySelectorAll(".puppet-frame")];
|
||||
if (!frames.length) return;
|
||||
|
||||
const activeIndex = ((index % frames.length) + frames.length) % frames.length;
|
||||
const transitionMs = Math.max(80, Number(loadedRig?.activeGesture?.transitionMs || 170));
|
||||
const current = frames.find(frame => frame.classList.contains("is-active"));
|
||||
const next = frames[activeIndex];
|
||||
if (!next || current === next) return;
|
||||
|
||||
for (const frame of frames) {
|
||||
frame.classList.toggle("is-active", Number(frame.dataset.frameIndex) === activeIndex);
|
||||
if (frame !== current && frame !== next) {
|
||||
hideInactiveFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
current.classList.remove("is-active", "is-entering");
|
||||
current.classList.add("is-exiting");
|
||||
current.style.transitionDuration = `${transitionMs}ms`;
|
||||
current.style.zIndex = "2";
|
||||
current.style.opacity = "0";
|
||||
current.style.transform = motionTransform(frameForElement(current), "exit");
|
||||
window.setTimeout(() => {
|
||||
if (current.classList.contains("is-exiting")) hideInactiveFrame(current);
|
||||
}, transitionMs + 40);
|
||||
}
|
||||
|
||||
next.classList.remove("is-exiting");
|
||||
next.classList.add("is-entering");
|
||||
next.style.transitionDuration = `${transitionMs}ms`;
|
||||
next.style.zIndex = "3";
|
||||
next.style.opacity = "0";
|
||||
next.style.transform = motionTransform(frameForElement(next), "enter");
|
||||
void next.offsetWidth;
|
||||
next.classList.add("is-active");
|
||||
window.requestAnimationFrame(() => {
|
||||
next.style.opacity = "1";
|
||||
next.style.transform = motionTransform(frameForElement(next), "target");
|
||||
});
|
||||
}
|
||||
|
||||
function startPoseSequence(gesture) {
|
||||
stopGestureTimer();
|
||||
setActiveGestureFrame(0);
|
||||
const frameMs = Math.max(80, Number(gesture.frameMs || 260));
|
||||
loadedRig.activeGesture = gesture;
|
||||
const first = puppet.querySelector('.puppet-frame[data-frame-index="0"]');
|
||||
if (first) {
|
||||
first.style.transitionDuration = "0ms";
|
||||
first.style.opacity = "1";
|
||||
first.style.zIndex = "3";
|
||||
first.style.transform = motionTransform(frameForElement(first), "target");
|
||||
first.classList.add("is-active");
|
||||
}
|
||||
|
||||
const frameMs = Math.max(120, Number(gesture.frameMs || 340));
|
||||
if (gesture.loop !== false && (gesture.frames?.length || 0) > 1) {
|
||||
gestureTimer = window.setInterval(() => {
|
||||
gestureFrame += 1;
|
||||
@@ -123,6 +189,7 @@ function renderPoseSequence(rig, active, gesture) {
|
||||
img.src = `${poseBase}${frame.image}?v=${assetVersion}`;
|
||||
img.alt = "";
|
||||
img.decoding = "async";
|
||||
img.style.transform = motionTransform(frame, "enter");
|
||||
fragment.appendChild(img);
|
||||
}
|
||||
puppet.replaceChildren(fragment);
|
||||
@@ -256,3 +323,4 @@ window.addEventListener("DOMContentLoaded", async () => {
|
||||
setState("idle");
|
||||
post("hostReady");
|
||||
});
|
||||
|
||||
|
||||
@@ -76,13 +76,20 @@ body {
|
||||
.puppet-frame {
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
transform-origin: 50% 38%;
|
||||
transform-origin: 50% 40%;
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 180ms;
|
||||
transition-timing-function: cubic-bezier(.22,.84,.32,1);
|
||||
}
|
||||
|
||||
.puppet-frame.is-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.puppet-frame.is-exiting {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#puppet[data-mode="pose-sequence"] .puppet-part {
|
||||
display: none;
|
||||
}
|
||||
@@ -456,3 +463,4 @@ body {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user