fix(pets): dynamically position cat at PetZone DOM element
Cat position is now measured from the [data-petzone] DOM element's getBoundingClientRect(), so it always aligns with the PetZone section regardless of window size or sidebar content height. Removed Live2DCat import from PetZone (cat renders on shared stage).
This commit is contained in:
@@ -12,49 +12,38 @@ interface Props {
|
|||||||
* - Mochi the cat (PetZone, bottom of right sidebar)
|
* - Mochi the cat (PetZone, bottom of right sidebar)
|
||||||
*
|
*
|
||||||
* Canvas sits behind UI panels (z-0, pointer-events: none).
|
* Canvas sits behind UI panels (z-0, pointer-events: none).
|
||||||
|
* Cat position is dynamically measured from the [data-petzone] DOM element.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// --- Layout constants (must match Tailwind in App.tsx) ---
|
|
||||||
// Left sidebar: w-72 = 288px, gap-4 = 16px, px-4 = 16px
|
|
||||||
// Right sidebar: w-64 = 256px, bottom bar: ~40px
|
|
||||||
const PAD = 16;
|
const PAD = 16;
|
||||||
const LEFT_W = 288;
|
const LEFT_W = 288;
|
||||||
const GAP = 16;
|
const GAP = 16;
|
||||||
const RIGHT_W = 256;
|
const RIGHT_W = 256;
|
||||||
const BOTTOM_BAR_H = 40;
|
|
||||||
|
|
||||||
function positionModels(
|
function positionKira(kira: any, appW: number, appH: number) {
|
||||||
kira: any, cat: any,
|
|
||||||
appW: number, appH: number,
|
|
||||||
) {
|
|
||||||
// Center panel center
|
|
||||||
const centerLeft = PAD + LEFT_W + GAP;
|
const centerLeft = PAD + LEFT_W + GAP;
|
||||||
const centerRight = appW - GAP - RIGHT_W - PAD;
|
const centerRight = appW - GAP - RIGHT_W - PAD;
|
||||||
const centerMidX = (centerLeft + centerRight) / 2;
|
const centerMidX = (centerLeft + centerRight) / 2;
|
||||||
const centerMidY = appH * 0.46;
|
const centerMidY = appH * 0.46;
|
||||||
|
|
||||||
if (kira) {
|
const s = Math.min(((centerRight - centerLeft) * 0.78) / kira.width, (appH * 0.78) / kira.height);
|
||||||
const s = Math.min(((centerRight - centerLeft) * 0.78) / kira.width, (appH * 0.78) / kira.height);
|
kira.scale.set(s);
|
||||||
kira.scale.set(s);
|
kira.anchor.set(0.5, 0.5);
|
||||||
kira.anchor.set(0.5, 0.5);
|
kira.position.set(centerMidX, centerMidY);
|
||||||
kira.position.set(centerMidX, centerMidY);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Right sidebar center
|
function positionCat(cat: any) {
|
||||||
const rightLeft = appW - RIGHT_W - PAD;
|
const el = document.querySelector('[data-petzone]');
|
||||||
const rightMidX = rightLeft + RIGHT_W / 2;
|
if (!el || !cat) return;
|
||||||
const catY = appH - BOTTOM_BAR_H - 50;
|
const r = el.getBoundingClientRect();
|
||||||
|
|
||||||
if (cat) {
|
cat.scale.set(1);
|
||||||
// Set scale to 1 first to get natural bounds, then compute target
|
const naturalH = cat.getLocalBounds().height;
|
||||||
cat.scale.set(1);
|
const targetPx = 100;
|
||||||
const naturalH = cat.getLocalBounds().height;
|
const cs = naturalH > 0 ? targetPx / naturalH : 0.05;
|
||||||
const targetPx = 100; // ~100px tall rendered
|
cat.scale.set(cs);
|
||||||
const cs = naturalH > 0 ? targetPx / naturalH : 0.05;
|
cat.anchor.set(0.5, 0.5);
|
||||||
cat.scale.set(cs);
|
cat.position.set(r.left + r.width / 2, r.top + r.height / 2 + 10);
|
||||||
cat.anchor.set(0.5, 0.5);
|
|
||||||
cat.position.set(rightMidX, catY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Live2DStage({ onKiraReady, onReady }: Props) {
|
export default function Live2DStage({ onKiraReady, onReady }: Props) {
|
||||||
@@ -92,7 +81,8 @@ export default function Live2DStage({ onKiraReady, onReady }: Props) {
|
|||||||
|
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
app.renderer.resize(window.innerWidth, window.innerHeight);
|
app.renderer.resize(window.innerWidth, window.innerHeight);
|
||||||
positionModels(kiraModel, catModel, app.screen.width, app.screen.height);
|
if (kiraModel) positionKira(kiraModel, app.screen.width, app.screen.height);
|
||||||
|
if (catModel) positionCat(catModel);
|
||||||
};
|
};
|
||||||
window.addEventListener('resize', onResize);
|
window.addEventListener('resize', onResize);
|
||||||
|
|
||||||
@@ -102,7 +92,7 @@ export default function Live2DStage({ onKiraReady, onReady }: Props) {
|
|||||||
});
|
});
|
||||||
if (!mounted) { app.destroy(true); return; }
|
if (!mounted) { app.destroy(true); return; }
|
||||||
|
|
||||||
positionModels(kiraModel, null, app.screen.width, app.screen.height);
|
positionKira(kiraModel, app.screen.width, app.screen.height);
|
||||||
(kiraModel as any).isInteractive = () => false;
|
(kiraModel as any).isInteractive = () => false;
|
||||||
app.stage.addChild(kiraModel as any);
|
app.stage.addChild(kiraModel as any);
|
||||||
|
|
||||||
@@ -118,7 +108,9 @@ export default function Live2DStage({ onKiraReady, onReady }: Props) {
|
|||||||
});
|
});
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
positionModels(kiraModel, catModel, app.screen.width, app.screen.height);
|
// Small delay to ensure PetZone DOM is rendered
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
positionCat(catModel);
|
||||||
(catModel as any).isInteractive = () => false;
|
(catModel as any).isInteractive = () => false;
|
||||||
app.stage.addChild(catModel as any);
|
app.stage.addChild(catModel as any);
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import Live2DCat from './Live2DCat';
|
|
||||||
|
|
||||||
export default function PetZone() {
|
export default function PetZone() {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 relative overflow-hidden" style={{ minHeight: 160 }}>
|
<div data-petzone className="p-4 relative overflow-hidden" style={{ minHeight: 140 }}>
|
||||||
<h3 className="text-sm font-bold text-kira-plum mb-2 flex items-center gap-2">
|
<h3 className="text-sm font-bold text-kira-plum mb-2 flex items-center gap-2">
|
||||||
<span>🐱</span> Pet Zone
|
<span>🐱</span> Pet Zone
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center pt-2">
|
||||||
<Live2DCat className="w-28 h-28" />
|
<span className="text-[10px] text-kira-plum/60 font-medium">Mochi</span>
|
||||||
<span className="text-[10px] text-kira-plum/60 mt-1 font-medium">Mochi</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user