aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 13:13:01 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-10-05 13:13:01 +0900
commite22218a01f418fffc89d0c16d88ada84a764b448 (patch)
tree99819e329aa3eaffeadb5f78004b193feafb7915
parent71a1e45dae4548a3096a51ab4c0262e12c7203fa (diff)
bluebey-studio: スマホのパネルをハンドルのドラッグで上下に動かせるように
-rw-r--r--public/bluebey-studio/src/main.js50
-rw-r--r--public/bluebey-studio/src/style.css33
2 files changed, 74 insertions, 9 deletions
diff --git a/public/bluebey-studio/src/main.js b/public/bluebey-studio/src/main.js
index e282e9d..49494a4 100644
--- a/public/bluebey-studio/src/main.js
+++ b/public/bluebey-studio/src/main.js
@@ -1356,6 +1356,56 @@ async function init() {
const panel = buildPanel(app, document.getElementById('panel'));
app.panel = panel;
+ // On a phone the panel is a bottom sheet. Its grab handle drags the sheet's top
+ // edge up and down, so the menu can be pulled out of the way of the character.
+ const panelEl = document.getElementById('panel');
+ const panelGrip = document.createElement('div');
+ panelGrip.className = 'panel-grip';
+ panelGrip.setAttribute('aria-hidden', 'true');
+ panelEl.prepend(panelGrip);
+ installSheetDrag(panelEl, panelGrip);
+
+ /** Drag on the phone sheet's handle; a no-op anywhere else. */
+ function installSheetDrag(sheet, grip) {
+ const phone = window.matchMedia('(max-width: 768px), (max-height: 500px) and (pointer: coarse)');
+ let drag = null;
+ const minHeight = () => Math.round(window.innerHeight * 0.16);
+ const maxHeight = () => Math.round(window.innerHeight * 0.94);
+
+ grip.addEventListener('pointerdown', (event) => {
+ if (!phone.matches) return;
+ const rect = sheet.getBoundingClientRect();
+ // Start from the height the sheet has now, so the first move does not jump.
+ drag = { id: event.pointerId, fromY: event.clientY, startH: rect.height };
+ sheet.style.maxHeight = 'none';
+ sheet.style.height = `${rect.height}px`;
+ try { grip.setPointerCapture(event.pointerId); } catch { /* capture is optional */ }
+ event.preventDefault();
+ });
+ grip.addEventListener('pointermove', (event) => {
+ if (!drag || event.pointerId !== drag.id) return;
+ // Dragging up grows the sheet (its top edge climbs); dragging down shrinks it.
+ const next = drag.startH + (drag.fromY - event.clientY);
+ sheet.style.height = `${Math.min(maxHeight(), Math.max(minHeight(), next))}px`;
+ event.preventDefault();
+ });
+ const end = (event) => {
+ if (!drag || (event && event.pointerId !== drag.id)) return;
+ drag = null;
+ };
+ grip.addEventListener('pointerup', end);
+ grip.addEventListener('pointercancel', end);
+
+ // A window grown to the desktop layout keeps the side column: drop the height
+ // the drag had fixed, so the panel goes back to filling the screen.
+ const clearOnDesktop = () => {
+ if (phone.matches) return;
+ sheet.style.height = '';
+ sheet.style.maxHeight = '';
+ };
+ window.addEventListener('resize', clearOnDesktop);
+ }
+
Object.assign(app.actions, {
capturePose,
applyState,
diff --git a/public/bluebey-studio/src/style.css b/public/bluebey-studio/src/style.css
index efd0a18..f4c9270 100644
--- a/public/bluebey-studio/src/style.css
+++ b/public/bluebey-studio/src/style.css
@@ -132,6 +132,10 @@ body:not(.panel-hidden) #stage {
#panel::-webkit-scrollbar { width: 9px; }
#panel::-webkit-scrollbar-thumb { background: #d8d1e8; border-radius: 9px; }
+/* The phone sheet's grab handle is a real element (built in the app) so it can
+ carry a drag; it is hidden everywhere else. */
+.panel-grip { display: none; }
+
body.panel-hidden #panel { display: none; }
body.panel-hidden #topbar { max-width: calc(100vw - 48px); }
@@ -203,33 +207,44 @@ body.panel-hidden #topbar { max-width: calc(100vw - 48px); }
bottom: 0;
width: auto;
max-height: 65dvh;
- padding: 20px 14px calc(20px + env(safe-area-inset-bottom));
+ padding: 0 14px calc(20px + env(safe-area-inset-bottom));
border-left: 0;
border-top: 1px solid var(--border);
border-radius: 18px 18px 0 0;
box-shadow: 0 -6px 24px rgba(40, 20, 80, 0.18);
}
- /* A grab handle so the sheet reads as an edge that opens and closes; it
- sticks to the top of the scroll area as the sheet is scrolled. */
- #panel::before {
- content: "";
+ /* The grab handle: sticky at the top of the scroll area, so it stays grabbable
+ however far the menu is scrolled, and `touch-action: none` so a drag on it
+ resizes the sheet instead of scrolling it. */
+ .panel-grip {
+ display: flex;
+ align-items: center;
+ justify-content: center;
position: sticky;
top: 0;
- display: block;
+ z-index: 6;
+ height: 24px;
+ margin: 0 -14px;
+ background: var(--card);
+ touch-action: none;
+ cursor: grab;
+ }
+ .panel-grip::after {
+ content: "";
width: 44px;
height: 5px;
- margin: 0 auto 12px;
border-radius: 999px;
background: #cfc6e4;
}
+ .panel-grip:active { cursor: grabbing; }
body.panel-hidden #topbar { max-width: none; }
/* The grab handle is sticky at the top of the sheet, so the action bar and the
tabs start below it. */
- .panel-topbar { top: 17px; }
- .tabs { top: 51px; }
+ .panel-topbar { top: 24px; }
+ .tabs { top: 58px; }
}
/* ----------------------------------------------------------------- sections */