Skip to content

Commit bb66a9f

Browse files
committed
improve mobile modtext edit
1 parent 9782ac0 commit bb66a9f

File tree

1 file changed

+215
-18
lines changed

1 file changed

+215
-18
lines changed

src/html/modtextedit.html

Lines changed: 215 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<!doctype html>
22
<html>
3+
<meta
4+
name="viewport"
5+
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
6+
/>
37
<head>
48
<style>
59
canvas {
@@ -22,6 +26,7 @@
2226
color-scheme: dark;
2327
background-color: #15171c;
2428
color: white;
29+
margin-left: 0px;
2530
}
2631
#textarea {
2732
width: 95%;
@@ -35,9 +40,18 @@
3540
}
3641
#canvasContainer {
3742
position: relative;
38-
width: 960px; /* 1920 * 0.5 */
39-
height: 540px; /* 1080 * 0.5 */
40-
border: 2px solid #333;
43+
width: 100vw;
44+
height: 90vh;
45+
overflow: hidden;
46+
}
47+
48+
#scaler {
49+
position: absolute;
50+
top: 0;
51+
left: 0;
52+
width: 1920px;
53+
height: 1080px;
54+
transform-origin: top left;
4155
}
4256

4357
#player {
@@ -87,6 +101,9 @@
87101
.actions {
88102
text-align: right;
89103
}
104+
canvas {
105+
border: none;
106+
}
90107
</style>
91108
<link
92109
href="https://fonts.googleapis.com/icon?family=Material+Icons"
@@ -210,12 +227,11 @@ <h3>Select an image</h3>
210227
</form>
211228
</dialog>
212229
</div>
213-
<div
214-
id="canvasContainer"
215-
style="border: 2px solid #333; display: inline-block"
216-
>
217-
<div id="player"></div>
218-
<canvas id="editorCanvas" style="display: none"></canvas>
230+
<div id="canvasContainer" style="display: inline-block">
231+
<div id="scaler">
232+
<div id="player"></div>
233+
<canvas id="editorCanvas" style="display: none"></canvas>
234+
</div>
219235
</div>
220236

221237
<textarea style="display: none" id="textarea" rows="10"></textarea>
@@ -224,19 +240,22 @@ <h3>Select an image</h3>
224240
const dialog = document.getElementById("imageDialog");
225241
const grid = document.getElementById("imageGrid");
226242
const form = document.getElementById("imageUploadForm");
227-
228-
fabric.FabricObject.customProperties = ["dataId", "counterName"];
229-
let data = {};
230243
const playPauseButton = document.getElementById("playPauseButton");
231244
const player = new Twitch.Player("player", {
232-
width: 1920 / 2,
233-
height: 1080 / 2,
245+
width: 1920,
246+
height: 1080,
234247
channel: "sweetbabooo_o",
235248
parent: ["localhost", "talkingpanda.dev"],
236249
autoplay: true,
237250
muted: true,
238251
});
239-
player.addEventListener(Twitch.Player.PLAYING, () => {
252+
let viewScale = 1;
253+
let panX = 0;
254+
let panY = 0;
255+
fabric.FabricObject.customProperties = ["dataId", "counterName"];
256+
let data = {};
257+
258+
player.addEventListener(Twitch.Player.READY, () => {
240259
if (!canvas) initCanvas();
241260
});
242261

@@ -286,14 +305,191 @@ <h3>Select an image</h3>
286305
ctx.drawImage(deleteImg, -size / 2, -size / 2, size, size);
287306
ctx.restore();
288307
}
308+
309+
function enablePinchAndPan(canvas) {
310+
let lastDist = null;
311+
let lastMid = null;
312+
let startScale = viewScale;
313+
let startPanX = panX;
314+
let startPanY = panY;
315+
316+
const el = canvas.upperCanvasEl;
317+
318+
el.addEventListener(
319+
"touchstart",
320+
(e) => {
321+
if (e.touches.length === 2) {
322+
e.preventDefault();
323+
324+
lastDist = getDistance(e.touches[0], e.touches[1]);
325+
lastMid = getMidpoint(e.touches[0], e.touches[1]);
326+
327+
startScale = viewScale;
328+
startPanX = panX;
329+
startPanY = panY;
330+
canvas.selection = false;
331+
canvas.discardActiveObject();
332+
}
333+
},
334+
{ passive: false },
335+
);
336+
337+
el.addEventListener(
338+
"touchmove",
339+
(e) => {
340+
if (e.touches.length !== 2 || lastDist === null) return;
341+
342+
e.preventDefault();
343+
344+
const dist = getDistance(e.touches[0], e.touches[1]);
345+
const mid = getMidpoint(e.touches[0], e.touches[1]);
346+
347+
/* ----- SCALE ----- */
348+
if (Math.abs(dist - lastDist) > 25) {
349+
const scaleFactor = dist / lastDist;
350+
const oldScale = viewScale;
351+
viewScale = clamp(startScale * scaleFactor, 0.25, 3);
352+
zoomAtCursor(mid.x, mid.y, oldScale, viewScale);
353+
} else {
354+
/* ----- PAN ----- */
355+
panX = startPanX + (mid.x - lastMid.x);
356+
panY = startPanY + (mid.y - lastMid.y);
357+
}
358+
359+
clampPan();
360+
applyTransform();
361+
},
362+
{ passive: false },
363+
);
364+
365+
el.addEventListener("touchend", () => {
366+
lastDist = null;
367+
lastMid = null;
368+
canvas.selection = true;
369+
});
370+
}
371+
372+
function clampPan() {
373+
const container = document.getElementById("canvasContainer");
374+
375+
const maxX = 0;
376+
const maxY = 0;
377+
378+
const minX = container.clientWidth - 1920 * viewScale;
379+
const minY = container.clientHeight - 1080 * viewScale;
380+
381+
panX = clamp(panX, minX, maxX);
382+
panY = clamp(panY, minY, maxY);
383+
}
384+
385+
function clamp(v, min, max) {
386+
return Math.min(Math.max(v, min), max);
387+
}
388+
389+
function getDistance(t1, t2) {
390+
const dx = t2.clientX - t1.clientX;
391+
const dy = t2.clientY - t1.clientY;
392+
return Math.hypot(dx, dy);
393+
}
394+
395+
function getMidpoint(t1, t2) {
396+
return {
397+
x: (t1.clientX + t2.clientX) / 2,
398+
y: (t1.clientY + t2.clientY) / 2,
399+
};
400+
}
401+
402+
function enableMousePanAndZoom(canvas) {
403+
const el = canvas.upperCanvasEl;
404+
405+
let isPanning = false;
406+
let startX = 0;
407+
let startY = 0;
408+
let startPanX = 0;
409+
let startPanY = 0;
410+
411+
el.addEventListener("mousedown", (e) => {
412+
if (e.button !== 1) return; // Mouse3 only
413+
e.preventDefault();
414+
415+
isPanning = true;
416+
startX = e.clientX;
417+
startY = e.clientY;
418+
startPanX = panX;
419+
startPanY = panY;
420+
canvas.selection = false;
421+
canvas.discardActiveObject();
422+
});
423+
424+
window.addEventListener("mousemove", (e) => {
425+
if (!isPanning) return;
426+
427+
panX = startPanX + (e.clientX - startX);
428+
panY = startPanY + (e.clientY - startY);
429+
430+
clampPan();
431+
applyTransform();
432+
});
433+
434+
window.addEventListener("mouseup", (e) => {
435+
if (e.button === 1) {
436+
isPanning = false;
437+
canvas.selection = true;
438+
}
439+
});
440+
441+
/* ---------- CTRL + SCROLL ZOOM ---------- */
442+
443+
el.addEventListener(
444+
"wheel",
445+
(e) => {
446+
if (!e.ctrlKey) return;
447+
448+
e.preventDefault();
449+
450+
const zoomIntensity = 0.0015;
451+
const delta = -e.deltaY * zoomIntensity;
452+
453+
const oldScale = viewScale;
454+
viewScale = clamp(viewScale * (1 + delta), 0.5, 3);
455+
456+
zoomAtCursor(e.clientX, e.clientY, oldScale, viewScale);
457+
clampPan();
458+
applyTransform();
459+
},
460+
{ passive: false },
461+
);
462+
}
463+
464+
function zoomAtCursor(clientX, clientY, oldScale, newScale) {
465+
const container = document.getElementById("canvasContainer");
466+
const rect = container.getBoundingClientRect();
467+
468+
const cx = clientX - rect.left;
469+
const cy = clientY - rect.top;
470+
471+
const scaleRatio = newScale / oldScale;
472+
473+
panX = cx - scaleRatio * (cx - panX);
474+
panY = cy - scaleRatio * (cy - panY);
475+
}
476+
477+
function applyTransform() {
478+
const scaler = document.getElementById("scaler");
479+
480+
scaler.style.transform = `translate(${panX}px, ${panY}px) scale(${viewScale})`;
481+
482+
canvas.requestRenderAll();
483+
}
484+
289485
function initCanvas() {
290486
document.getElementById("editorCanvas").style.display = "block";
291487
canvas = new fabric.Canvas("editorCanvas", {
292488
width: 1920,
293489
height: 1080,
294490
});
295-
canvas.setZoom(0.5);
296-
canvas.setDimensions({ width: 1920 * 0.5, height: 1080 * 0.5 });
491+
enablePinchAndPan(canvas);
492+
enableMousePanAndZoom(canvas);
297493
getModText().then(() => {
298494
events.forEach((event) => canvas.on(event, () => saveState()));
299495
});
@@ -474,7 +670,8 @@ <h3>Select an image</h3>
474670

475671
async function getModText() {
476672
if (!mode) {
477-
await getModTextCanvas();
673+
if (!canvas) initCanvas();
674+
else await getModTextCanvas();
478675
return;
479676
}
480677
try {

0 commit comments

Comments
 (0)