CC Company commited on
Commit
ff9e592
·
1 Parent(s): 165bdd3

✨ オフセット調整UIの改善・ドラッグ対応

Browse files

- オフセット値をスライダーで直感的に調整可能に
- プレビューcanvas上でドラッグ操作によるオフセット変更を実装
- プレビュー幅を80%に拡大しUX向上

Files changed (1) hide show
  1. index.html +175 -9
index.html CHANGED
@@ -207,6 +207,8 @@
207
  display: flex;
208
  align-items: center;
209
  justify-content: center;
 
 
210
  }
211
 
212
  .preview-grid {
@@ -398,19 +400,23 @@
398
  <div class="settings-grid">
399
  <div class="setting-item">
400
  <label for="offsetLeft">左オフセット(px)</label>
401
- <input type="number" id="offsetLeft" value="0" min="0">
 
402
  </div>
403
  <div class="setting-item">
404
  <label for="offsetRight">右オフセット(px)</label>
405
- <input type="number" id="offsetRight" value="0" min="0">
 
406
  </div>
407
  <div class="setting-item">
408
  <label for="offsetTop">上オフセット(px)</label>
409
- <input type="number" id="offsetTop" value="0" min="0">
 
410
  </div>
411
  <div class="setting-item">
412
  <label for="offsetBottom">下オフセット(px)</label>
413
- <input type="number" id="offsetBottom" value="0" min="0">
 
414
  </div>
415
  </div>
416
  </div>
@@ -448,19 +454,49 @@
448
  const resultGrid = document.getElementById('resultGrid');
449
  const progressBar = document.getElementById('progressBar');
450
  const progressFill = document.getElementById('progressFill');
451
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  // ファイルアップロード処理
453
  uploadArea.addEventListener('click', () => fileInput.click());
454
  uploadArea.addEventListener('dragover', handleDragOver);
455
  uploadArea.addEventListener('drop', handleDrop);
456
  uploadArea.addEventListener('dragleave', handleDragLeave);
457
  fileInput.addEventListener('change', handleFileSelect);
458
-
459
  // 設定変更時のプレビュー更新
460
- ['columns', 'rows', 'offsetLeft', 'offsetRight', 'offsetTop', 'offsetBottom'].forEach(id => {
461
  document.getElementById(id).addEventListener('input', updatePreview);
462
  });
463
-
464
  splitButton.addEventListener('click', splitImage);
465
  downloadAllButton.addEventListener('click', downloadAll);
466
 
@@ -524,7 +560,8 @@
524
  const splitHeight = originalImage.height - offsetTop - offsetBottom;
525
 
526
  // プレビューcanvasサイズ
527
- const previewMaxWidth = 400;
 
528
  const previewScale = Math.min(1, previewMaxWidth / originalImage.width);
529
  const canvasWidth = originalImage.width * previewScale;
530
  const canvasHeight = originalImage.height * previewScale;
@@ -564,6 +601,135 @@
564
  ctx.lineWidth = 2;
565
 
566
  // 垂直線
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  for (let i = 1; i < columns; i++) {
568
  const x = offsetLeft + (i * splitWidth) / columns;
569
  const xScaled = x * previewScale;
 
207
  display: flex;
208
  align-items: center;
209
  justify-content: center;
210
+ width: 80%;
211
+ margin: 0 auto;
212
  }
213
 
214
  .preview-grid {
 
400
  <div class="settings-grid">
401
  <div class="setting-item">
402
  <label for="offsetLeft">左オフセット(px)</label>
403
+ <input type="number" id="offsetLeft" value="0" min="0" style="margin-bottom:8px;">
404
+ <input type="range" id="offsetLeftSlider" value="0" min="0" max="500">
405
  </div>
406
  <div class="setting-item">
407
  <label for="offsetRight">右オフセット(px)</label>
408
+ <input type="number" id="offsetRight" value="0" min="0" style="margin-bottom:8px;">
409
+ <input type="range" id="offsetRightSlider" value="0" min="0" max="500">
410
  </div>
411
  <div class="setting-item">
412
  <label for="offsetTop">上オフセット(px)</label>
413
+ <input type="number" id="offsetTop" value="0" min="0" style="margin-bottom:8px;">
414
+ <input type="range" id="offsetTopSlider" value="0" min="0" max="500">
415
  </div>
416
  <div class="setting-item">
417
  <label for="offsetBottom">下オフセット(px)</label>
418
+ <input type="number" id="offsetBottom" value="0" min="0" style="margin-bottom:8px;">
419
+ <input type="range" id="offsetBottomSlider" value="0" min="0" max="500">
420
  </div>
421
  </div>
422
  </div>
 
454
  const resultGrid = document.getElementById('resultGrid');
455
  const progressBar = document.getElementById('progressBar');
456
  const progressFill = document.getElementById('progressFill');
457
+
458
+ // オフセットスライダー要素取得
459
+ const offsetLeftInput = document.getElementById('offsetLeft');
460
+ const offsetLeftSlider = document.getElementById('offsetLeftSlider');
461
+ const offsetRightInput = document.getElementById('offsetRight');
462
+ const offsetRightSlider = document.getElementById('offsetRightSlider');
463
+ const offsetTopInput = document.getElementById('offsetTop');
464
+ const offsetTopSlider = document.getElementById('offsetTopSlider');
465
+ const offsetBottomInput = document.getElementById('offsetBottom');
466
+ const offsetBottomSlider = document.getElementById('offsetBottomSlider');
467
+
468
+ // input <-> slider 双方向同期
469
+ function bindOffsetSync(input, slider) {
470
+ input.addEventListener('input', () => {
471
+ slider.value = input.value;
472
+ updatePreview();
473
+ });
474
+ slider.addEventListener('input', () => {
475
+ input.value = slider.value;
476
+ updatePreview();
477
+ });
478
+ }
479
+ bindOffsetSync(offsetLeftInput, offsetLeftSlider);
480
+ bindOffsetSync(offsetRightInput, offsetRightSlider);
481
+ bindOffsetSync(offsetTopInput, offsetTopSlider);
482
+ bindOffsetSync(offsetBottomInput, offsetBottomSlider);
483
+
484
+ // プレビューセクションの幅を80%に
485
+ previewContainer.style.width = "80%";
486
+ previewContainer.style.margin = "0 auto";
487
+
488
  // ファイルアップロード処理
489
  uploadArea.addEventListener('click', () => fileInput.click());
490
  uploadArea.addEventListener('dragover', handleDragOver);
491
  uploadArea.addEventListener('drop', handleDrop);
492
  uploadArea.addEventListener('dragleave', handleDragLeave);
493
  fileInput.addEventListener('change', handleFileSelect);
494
+
495
  // 設定変更時のプレビュー更新
496
+ ['columns', 'rows'].forEach(id => {
497
  document.getElementById(id).addEventListener('input', updatePreview);
498
  });
499
+
500
  splitButton.addEventListener('click', splitImage);
501
  downloadAllButton.addEventListener('click', downloadAll);
502
 
 
560
  const splitHeight = originalImage.height - offsetTop - offsetBottom;
561
 
562
  // プレビューcanvasサイズ
563
+ // プレビュー最大幅を80%に
564
+ const previewMaxWidth = previewContainer.offsetWidth > 0 ? previewContainer.offsetWidth * 0.8 : 600;
565
  const previewScale = Math.min(1, previewMaxWidth / originalImage.width);
566
  const canvasWidth = originalImage.width * previewScale;
567
  const canvasHeight = originalImage.height * previewScale;
 
601
  ctx.lineWidth = 2;
602
 
603
  // 垂直線
604
+ // --- オフセットラインをcanvas上でドラッグ操作するための処理 ---
605
+ // ドラッグ対象: left, right, top, bottom
606
+ const DRAG_MARGIN = 10; // ハンドルの感知範囲(px)
607
+ let dragging = null; // 'left'|'right'|'top'|'bottom'|null
608
+ let dragStart = {x:0, y:0};
609
+ let dragOffsetStart = {left:0, right:0, top:0, bottom:0};
610
+
611
+ // プレビューcanvasが存在する場合のみイベントを付与
612
+ canvas.style.cursor = "crosshair";
613
+ canvas.addEventListener('mousedown', function(e) {
614
+ const rect = canvas.getBoundingClientRect();
615
+ const x = (e.clientX - rect.left) / previewScale;
616
+ const y = (e.clientY - rect.top) / previewScale;
617
+
618
+ // どのラインが近いか判定
619
+ if (Math.abs(x - offsetLeft) < DRAG_MARGIN) {
620
+ dragging = 'left';
621
+ dragStart = {x, y};
622
+ dragOffsetStart = {
623
+ left: offsetLeft,
624
+ right: offsetRight,
625
+ top: offsetTop,
626
+ bottom: offsetBottom
627
+ };
628
+ canvas.style.cursor = "ew-resize";
629
+ } else if (Math.abs(x - (originalImage.width - offsetRight)) < DRAG_MARGIN) {
630
+ dragging = 'right';
631
+ dragStart = {x, y};
632
+ dragOffsetStart = {
633
+ left: offsetLeft,
634
+ right: offsetRight,
635
+ top: offsetTop,
636
+ bottom: offsetBottom
637
+ };
638
+ canvas.style.cursor = "ew-resize";
639
+ } else if (Math.abs(y - offsetTop) < DRAG_MARGIN) {
640
+ dragging = 'top';
641
+ dragStart = {x, y};
642
+ dragOffsetStart = {
643
+ left: offsetLeft,
644
+ right: offsetRight,
645
+ top: offsetTop,
646
+ bottom: offsetBottom
647
+ };
648
+ canvas.style.cursor = "ns-resize";
649
+ } else if (Math.abs(y - (originalImage.height - offsetBottom)) < DRAG_MARGIN) {
650
+ dragging = 'bottom';
651
+ dragStart = {x, y};
652
+ dragOffsetStart = {
653
+ left: offsetLeft,
654
+ right: offsetRight,
655
+ top: offsetTop,
656
+ bottom: offsetBottom
657
+ };
658
+ canvas.style.cursor = "ns-resize";
659
+ } else {
660
+ dragging = null;
661
+ }
662
+ });
663
+
664
+ window.addEventListener('mousemove', function(e) {
665
+ if (!dragging) return;
666
+ const rect = canvas.getBoundingClientRect();
667
+ const x = (e.clientX - rect.left) / previewScale;
668
+ const y = (e.clientY - rect.top) / previewScale;
669
+ if (dragging === 'left') {
670
+ let delta = x - dragStart.x;
671
+ let val = dragOffsetStart.left + delta;
672
+ val = Math.round(val);
673
+ val = Math.max(0, Math.min(originalImage.width - offsetRight - 1, val));
674
+ offsetLeftInput.value = val;
675
+ offsetLeftSlider.value = val;
676
+ } else if (dragging === 'right') {
677
+ let delta = dragStart.x - x;
678
+ let val = dragOffsetStart.right + delta;
679
+ val = Math.round(val);
680
+ val = Math.max(0, Math.min(originalImage.width - offsetLeft - 1, val));
681
+ offsetRightInput.value = val;
682
+ offsetRightSlider.value = val;
683
+ } else if (dragging === 'top') {
684
+ let delta = y - dragStart.y;
685
+ let val = dragOffsetStart.top + delta;
686
+ val = Math.round(val);
687
+ val = Math.max(0, Math.min(originalImage.height - offsetBottom - 1, val));
688
+ offsetTopInput.value = val;
689
+ offsetTopSlider.value = val;
690
+ } else if (dragging === 'bottom') {
691
+ let delta = dragStart.y - y;
692
+ let val = dragOffsetStart.bottom + delta;
693
+ val = Math.round(val);
694
+ val = Math.max(0, Math.min(originalImage.height - offsetTop - 1, val));
695
+ offsetBottomInput.value = val;
696
+ offsetBottomSlider.value = val;
697
+ }
698
+ updatePreview();
699
+ });
700
+
701
+ window.addEventListener('mouseup', function(e) {
702
+ if (dragging) {
703
+ dragging = null;
704
+ canvas.style.cursor = "crosshair";
705
+ }
706
+ });
707
+
708
+ // ハンドルを太く描画
709
+ ctx.save();
710
+ ctx.strokeStyle = "#e74c3c";
711
+ ctx.lineWidth = 6;
712
+ // left
713
+ ctx.beginPath();
714
+ ctx.moveTo(offsetLeft * previewScale, offsetTop * previewScale);
715
+ ctx.lineTo(offsetLeft * previewScale, (originalImage.height - offsetBottom) * previewScale);
716
+ ctx.stroke();
717
+ // right
718
+ ctx.beginPath();
719
+ ctx.moveTo((originalImage.width - offsetRight) * previewScale, offsetTop * previewScale);
720
+ ctx.lineTo((originalImage.width - offsetRight) * previewScale, (originalImage.height - offsetBottom) * previewScale);
721
+ ctx.stroke();
722
+ // top
723
+ ctx.beginPath();
724
+ ctx.moveTo(offsetLeft * previewScale, offsetTop * previewScale);
725
+ ctx.lineTo((originalImage.width - offsetRight) * previewScale, offsetTop * previewScale);
726
+ ctx.stroke();
727
+ // bottom
728
+ ctx.beginPath();
729
+ ctx.moveTo(offsetLeft * previewScale, (originalImage.height - offsetBottom) * previewScale);
730
+ ctx.lineTo((originalImage.width - offsetRight) * previewScale, (originalImage.height - offsetBottom) * previewScale);
731
+ ctx.stroke();
732
+ ctx.restore();
733
  for (let i = 1; i < columns; i++) {
734
  const x = offsetLeft + (i * splitWidth) / columns;
735
  const xScaled = x * previewScale;