Spaces:
				
			
			
	
			
			
		Runtime error
		
	
	
	
			
			
	
	
	
	
		
		
		Runtime error
		
	🔀 Merge: offset-slider-drag
Browse files- 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' | 
| 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 | 
            -
                         | 
|  | |
| 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;
         | 
