img-view / index.html
soiz1's picture
Update index.html
c315a38 verified
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>画像編集ツール</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
overflow: hidden;
height: 100vh;
--bg-color: #f0f0f0;
}
#preview-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
overflow: hidden;
position: relative;
}
#preview-image {
position: absolute;
cursor: move;
transform-origin: center center;
display: none;
}
.control-point {
position: absolute;
width: 10px;
height: 10px;
background-color: #4285f4;
border-radius: 50%;
cursor: pointer;
z-index: 10;
display: none;
}
.rotate-handle {
position: absolute;
width: 30px;
height: 30px;
background-color: #4285f4;
color: white;
border-radius: 50%;
display: none;
align-items: center;
justify-content: center;
font-size: 18px;
cursor: pointer;
z-index: 10;
font-family: sans-serif;
transform: translate(-20%, 0%);
}
.settings-panel {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
z-index: 100;
overflow: hidden;
}
.panel-header {
padding: 10px 15px;
background-color: #4285f4;
color: white;
cursor: move;
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-title {
font-weight: bold;
}
.toggle-panel {
background: none;
border: none;
color: white;
font-size: 16px;
cursor: pointer;
}
.panel-content {
padding: 15px;
transition: all 0.3s ease;
}
.panel-collapsed .panel-content {
display: none;
}
.form-group {
display: flex;
margin-bottom: 15px;
align-items: center;
}
.form-group label {
width: 80px;
font-weight: bold;
}
.form-group input {
width: 70px;
padding: 5px;
margin-right: 10px;
}
button {
padding: 8px 15px;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-right: 5px;
}
button:hover {
background-color: #3367d6;
}
.upload-section {
margin-bottom: 20px;
padding: 15px;
border: 2px dashed #ccc;
border-radius: 8px;
text-align: center;
}
.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: inherit;
z-index: 1000;
cursor: none !important;
}
.close-fullscreen {
position: fixed;
top: 20px;
right: 20px;
color: #333;
font-size: 30px;
cursor: pointer;
z-index: 1001;
background-color: rgba(255,255,255,0.7);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.color-picker {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.color-picker label {
width: 80px;
font-weight: bold;
}
.color-picker input {
width: 60px;
height: 30px;
padding: 0;
border: 1px solid #ddd;
}
.hidden {
display: none !important;
}
.flip-buttons {
display: flex;
margin-bottom: 15px;
}
.flip-buttons button {
flex: 1;
}
.pointer-lock {
cursor: none !important;
}
</style>
</head>
<body>
<div id="preview-container">
<img id="preview-image" style="-webkit-user-drag: none;">
<div class="rotate-handle" id="rotate-handle"></div>
<!-- コントロールポイントはJavaScriptで動的に追加 -->
</div>
<div class="settings-panel" id="settings-panel">
<div class="panel-header" id="panel-header">
<span class="panel-title">画像設定</span>
<button class="toggle-panel" id="toggle-panel"></button>
</div>
<div class="panel-content">
<div class="upload-section">
<input type="file" id="image-upload" accept="image/*">
<p>画像をドラッグ&ドロップ</p>
</div>
<div class="color-picker">
<label>背景色:</label>
<input type="color" id="bg-color" value="#f0f0f0">
</div>
<div class="form-group">
<label>X位置:</label>
<input type="number" id="pos-x">
<label>Y位置:</label>
<input type="number" id="pos-y">
</div>
<div class="form-group">
<label>幅:</label>
<input type="number" id="width">
<label>高さ:</label>
<input type="number" id="height">
</div>
<div class="form-group">
<label>回転:</label>
<input type="number" id="rotation" value="0">
<span></span>
</div>
<div class="flip-buttons">
<button id="flip-horizontal">左右反転</button>
<button id="flip-vertical">上下反転</button>
</div>
<button id="ok-button">全画面表示</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const uploadInput = document.getElementById('image-upload');
const previewContainer = document.getElementById('preview-container');
const previewImage = document.getElementById('preview-image');
const rotateHandle = document.getElementById('rotate-handle');
const posXInput = document.getElementById('pos-x');
const posYInput = document.getElementById('pos-y');
const widthInput = document.getElementById('width');
const heightInput = document.getElementById('height');
const rotationInput = document.getElementById('rotation');
const okButton = document.getElementById('ok-button');
const bgColorInput = document.getElementById('bg-color');
const flipHorizontalBtn = document.getElementById('flip-horizontal');
const flipVerticalBtn = document.getElementById('flip-vertical');
const settingsPanel = document.getElementById('settings-panel');
const panelHeader = document.getElementById('panel-header');
const togglePanel = document.getElementById('toggle-panel');
let isDragging = false;
let activeControlPoint = null;
let isRotating = false;
let isDraggingPanel = false;
let startX, startY;
let startWidth, startHeight;
let startRotation;
let startImageX, startImageY;
let startPanelX, startPanelY;
let aspectRatio = 1;
let isFullscreen = false;
let scaleX = 1;
let scaleY = 1;
// 背景色変更
bgColorInput.addEventListener('input', function() {
const bgColor = this.value;
previewContainer.style.backgroundColor = bgColor;
document.documentElement.style.setProperty('--bg-color', bgColor);
});
// コントロールポイントのタイプ
const ControlPointType = {
TOP_LEFT: 'top-left',
TOP_CENTER: 'top-center',
TOP_RIGHT: 'top-right',
MIDDLE_LEFT: 'middle-left',
MIDDLE_RIGHT: 'middle-right',
BOTTOM_LEFT: 'bottom-left',
BOTTOM_CENTER: 'bottom-center',
BOTTOM_RIGHT: 'bottom-right'
};
// 画像アップロード処理
uploadInput.addEventListener('change', handleImageUpload);
previewContainer.addEventListener('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
previewContainer.style.backgroundColor = '#e0e0e0';
});
previewContainer.addEventListener('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
previewContainer.style.backgroundColor = bgColorInput.value;
});
previewContainer.addEventListener('drop', function(e) {
e.preventDefault();
e.stopPropagation();
previewContainer.style.backgroundColor = bgColorInput.value;
if (e.dataTransfer.files.length) {
uploadInput.files = e.dataTransfer.files;
handleImageUpload();
}
});
function handleImageUpload() {
const file = uploadInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
previewImage.src = e.target.result;
previewImage.style.display = 'block';
rotateHandle.style.display = 'block';
previewImage.onload = function() {
// 初期設定
const containerWidth = previewContainer.clientWidth;
const containerHeight = previewContainer.clientHeight;
const imgWidth = previewImage.naturalWidth;
const imgHeight = previewImage.naturalHeight;
aspectRatio = imgWidth / imgHeight;
// コンテナに合わせて初期サイズを設定
let initWidth = Math.min(imgWidth, containerWidth * 0.8);
let initHeight = initWidth / aspectRatio;
if (initHeight > containerHeight * 0.8) {
initHeight = containerHeight * 0.8;
initWidth = initHeight * aspectRatio;
}
// 中央に配置
const initX = (containerWidth - initWidth) / 2;
const initY = (containerHeight - initHeight) / 2;
updateImageStyle(initX, initY, initWidth, initHeight, 0);
updateFormInputs(initX, initY, initWidth, initHeight, 0);
createControlPoints();
// コントロールポイントを表示
document.querySelectorAll('.control-point').forEach(point => {
point.style.display = 'block';
});
};
};
reader.readAsDataURL(file);
}
// コントロールポイントを作成
function createControlPoints() {
// 既存のコントロールポイントを削除
document.querySelectorAll('.control-point').forEach(el => el.remove());
// 8つのコントロールポイントを作成
const positions = [
{ type: ControlPointType.TOP_LEFT, left: 0, top: 0 },
{ type: ControlPointType.TOP_CENTER, left: 50, top: 0 },
{ type: ControlPointType.TOP_RIGHT, left: 100, top: 0 },
{ type: ControlPointType.MIDDLE_LEFT, left: 0, top: 50 },
{ type: ControlPointType.MIDDLE_RIGHT, left: 100, top: 50 },
{ type: ControlPointType.BOTTOM_LEFT, left: 0, top: 100 },
{ type: ControlPointType.BOTTOM_CENTER, left: 50, top: 100 },
{ type: ControlPointType.BOTTOM_RIGHT, left: 100, top: 100 }
];
positions.forEach(pos => {
const point = document.createElement('div');
point.className = 'control-point';
point.dataset.type = pos.type;
point.style.display = 'none'; // 初期状態では非表示
// 位置を設定(%単位)
point.style.left = `calc(${pos.left}% - 5px)`;
point.style.top = `calc(${pos.top}% - 5px)`;
// イベントリスナーを追加
point.addEventListener('mousedown', startControlPointDrag);
previewImage.parentNode.appendChild(point);
});
}
// 画像のスタイルを更新
function updateImageStyle(x, y, width, height, rotation) {
previewImage.style.left = `${x}px`;
previewImage.style.top = `${y}px`;
previewImage.style.width = `${width}px`;
previewImage.style.height = `${height}px`;
previewImage.style.transform = `rotate(${rotation}deg) scaleX(${scaleX}) scaleY(${scaleY})`;
// 回転ハンドルの位置を更新(画像の上中央)
const rotateX = x + width / 2 - 15;
const rotateY = y - 40;
rotateHandle.style.left = `${rotateX}px`;
rotateHandle.style.top = `${rotateY}px`;
// コントロールポイントの位置を更新
updateControlPointsPosition(x, y, width, height, rotation);
}
// コントロールポイントの位置を更新
function updateControlPointsPosition(x, y, width, height, rotation) {
const points = document.querySelectorAll('.control-point');
const centerX = x + width / 2;
const centerY = y + height / 2;
points.forEach(point => {
const type = point.dataset.type;
let pointX, pointY;
switch(type) {
case ControlPointType.TOP_LEFT:
pointX = x;
pointY = y;
break;
case ControlPointType.TOP_CENTER:
pointX = centerX - 5;
pointY = y;
break;
case ControlPointType.TOP_RIGHT:
pointX = x + width - 10;
pointY = y;
break;
case ControlPointType.MIDDLE_LEFT:
pointX = x;
pointY = centerY - 5;
break;
case ControlPointType.MIDDLE_RIGHT:
pointX = x + width - 10;
pointY = centerY - 5;
break;
case ControlPointType.BOTTOM_LEFT:
pointX = x;
pointY = y + height - 10;
break;
case ControlPointType.BOTTOM_CENTER:
pointX = centerX - 5;
pointY = y + height - 10;
break;
case ControlPointType.BOTTOM_RIGHT:
pointX = x + width - 10;
pointY = y + height - 10;
break;
}
// 回転を考慮した位置計算
if (rotation !== 0) {
const rad = rotation * Math.PI / 180;
const rotatedX = centerX + (pointX - centerX) * Math.cos(rad) - (pointY - centerY) * Math.sin(rad);
const rotatedY = centerY + (pointX - centerX) * Math.sin(rad) + (pointY - centerY) * Math.cos(rad);
pointX = rotatedX;
pointY = rotatedY;
}
point.style.left = `${pointX}px`;
point.style.top = `${pointY}px`;
});
}
// フォーム入力値を更新
function updateFormInputs(x, y, width, height, rotation) {
posXInput.value = Math.round(x);
posYInput.value = Math.round(y);
widthInput.value = Math.round(width);
heightInput.value = Math.round(height);
rotationInput.value = Math.round(rotation);
}
// 画像ドラッグ開始
previewImage.addEventListener('mousedown', function(e) {
if (!isFullscreen && e.target === previewImage) {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
startImageX = parseFloat(previewImage.style.left || '0');
startImageY = parseFloat(previewImage.style.top || '0');
e.preventDefault();
}
});
// コントロールポイントドラッグ開始
function startControlPointDrag(e) {
if (!isFullscreen) {
activeControlPoint = e.target.dataset.type;
startX = e.clientX;
startY = e.clientY;
startWidth = parseFloat(previewImage.style.width);
startHeight = parseFloat(previewImage.style.height);
startImageX = parseFloat(previewImage.style.left || '0');
startImageY = parseFloat(previewImage.style.top || '0');
e.preventDefault();
e.stopPropagation();
}
}
// 回転ハンドルドラッグ開始
rotateHandle.addEventListener('mousedown', function(e) {
if (!isFullscreen) {
isRotating = true;
startX = e.clientX;
startY = e.clientY;
startRotation = parseFloat(rotationInput.value) || 0;
e.preventDefault();
}
});
// パネルヘッダードラッグ開始
panelHeader.addEventListener('mousedown', function(e) {
if (!isFullscreen && (e.target === panelHeader || e.target.classList.contains('panel-title'))) {
isDraggingPanel = true;
startX = e.clientX;
startY = e.clientY;
startPanelX = settingsPanel.offsetLeft;
startPanelY = settingsPanel.offsetTop;
e.preventDefault();
}
});
// パネル折りたたみ/展開
togglePanel.addEventListener('click', function() {
if (!isFullscreen) {
settingsPanel.classList.toggle('panel-collapsed');
togglePanel.textContent = settingsPanel.classList.contains('panel-collapsed') ? '+' : '−';
}
});
// マウス移動イベント
document.addEventListener('mousemove', function(e) {
if (isFullscreen) return;
if (isDragging) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newX = startImageX + dx;
const newY = startImageY + dy;
const currentWidth = parseFloat(previewImage.style.width);
const currentHeight = parseFloat(previewImage.style.height);
const rotation = parseFloat(rotationInput.value) || 0;
updateImageStyle(newX, newY, currentWidth, currentHeight, rotation);
updateFormInputs(newX, newY, currentWidth, currentHeight, rotation);
} else if (activeControlPoint) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
let newWidth = startWidth;
let newHeight = startHeight;
let newX = startImageX;
let newY = startImageY;
const rotation = parseFloat(rotationInput.value) || 0;
switch(activeControlPoint) {
case ControlPointType.TOP_LEFT:
newWidth = startWidth - dx;
newHeight = newWidth / aspectRatio;
newX = startImageX + dx;
newY = startImageY + (startHeight - newHeight);
break;
case ControlPointType.TOP_CENTER:
newHeight = startHeight - dy;
newY = startImageY + dy;
break;
case ControlPointType.TOP_RIGHT:
newWidth = startWidth + dx;
newHeight = newWidth / aspectRatio;
newY = startImageY + (startHeight - newHeight);
break;
case ControlPointType.MIDDLE_LEFT:
newWidth = startWidth - dx;
newX = startImageX + dx;
break;
case ControlPointType.MIDDLE_RIGHT:
newWidth = startWidth + dx;
break;
case ControlPointType.BOTTOM_LEFT:
newWidth = startWidth - dx;
newHeight = newWidth / aspectRatio;
newX = startImageX + dx;
break;
case ControlPointType.BOTTOM_CENTER:
newHeight = startHeight + dy;
break;
case ControlPointType.BOTTOM_RIGHT:
newWidth = startWidth + dx;
newHeight = newWidth / aspectRatio;
break;
}
// 最小サイズ制限
newWidth = Math.max(10, newWidth);
newHeight = Math.max(10, newHeight);
updateImageStyle(newX, newY, newWidth, newHeight, rotation);
updateFormInputs(newX, newY, newWidth, newHeight, rotation);
} else if (isRotating) {
const centerX = parseFloat(previewImage.style.left) + parseFloat(previewImage.style.width) / 2;
const centerY = parseFloat(previewImage.style.top) + parseFloat(previewImage.style.height) / 2;
const angle = Math.atan2(e.clientY - centerY, e.clientX - centerX) * 180 / Math.PI;
const newRotation = (angle + 90) % 360; // 上向きを0度とする
const currentX = parseFloat(previewImage.style.left);
const currentY = parseFloat(previewImage.style.top);
const currentWidth = parseFloat(previewImage.style.width);
const currentHeight = parseFloat(previewImage.style.height);
updateImageStyle(currentX, currentY, currentWidth, currentHeight, newRotation);
updateFormInputs(currentX, currentY, currentWidth, currentHeight, newRotation);
} else if (isDraggingPanel) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
let newLeft = startPanelX + dx;
let newTop = startPanelY + dy;
// 画面外に出ないように制限
newLeft = Math.max(0, Math.min(window.innerWidth - settingsPanel.offsetWidth, newLeft));
newTop = Math.max(0, Math.min(window.innerHeight - panelHeader.offsetHeight, newTop));
settingsPanel.style.left = `${newLeft}px`;
settingsPanel.style.top = `${newTop}px`;
}
});
// マウスアップイベント
document.addEventListener('mouseup', function() {
isDragging = false;
activeControlPoint = null;
isRotating = false;
isDraggingPanel = false;
});
// フォーム入力の変更を画像に反映
posXInput.addEventListener('input', updateFromForm);
posYInput.addEventListener('input', updateFromForm);
widthInput.addEventListener('input', updateFromForm);
heightInput.addEventListener('input', updateFromForm);
rotationInput.addEventListener('input', updateFromForm);
function updateFromForm() {
const x = parseFloat(posXInput.value) || 0;
const y = parseFloat(posYInput.value) || 0;
const width = parseFloat(widthInput.value) || 100;
const height = parseFloat(heightInput.value) || 100;
const rotation = parseFloat(rotationInput.value) || 0;
updateImageStyle(x, y, width, height, rotation);
}
// 画像反転機能
flipHorizontalBtn.addEventListener('click', function() {
scaleX *= -1;
updateImageStyle(
parseFloat(previewImage.style.left),
parseFloat(previewImage.style.top),
parseFloat(previewImage.style.width),
parseFloat(previewImage.style.height),
parseFloat(rotationInput.value) || 0
);
});
flipVerticalBtn.addEventListener('click', function() {
scaleY *= -1;
updateImageStyle(
parseFloat(previewImage.style.left),
parseFloat(previewImage.style.top),
parseFloat(previewImage.style.width),
parseFloat(previewImage.style.height),
parseFloat(rotationInput.value) || 0
);
});
// 全画面表示/終了
okButton.addEventListener('click', toggleFullscreen);
function toggleFullscreen() {
if (!previewImage.src) return;
if (isFullscreen) {
exitFullscreen();
} else {
enterFullscreen();
}
}
function enterFullscreen() {
isFullscreen = true;
(document.documentElement.requestFullscreen || document.documentElement.webkitRequestFullscreen || document.documentElement.mozRequestFullScreen || document.documentElement.msRequestFullscreen).call(document.documentElement);
// 編集コントロールを非表示
document.querySelectorAll('.control-point').forEach(point => {
point.classList.add('hidden');
});
rotateHandle.classList.add('hidden');
settingsPanel.classList.add('hidden');
// プレビューコンテナにフルスクリーン用クラスを追加
previewContainer.classList.add('fullscreen-mode');
// 画像のドラッグを無効化
previewImage.style.cursor = 'default';
// 閉じるボタンを表示
const closeButton = document.createElement('div');
closeButton.className = 'close-fullscreen';
closeButton.innerHTML = '×';
closeButton.addEventListener('click', exitFullscreen);
document.body.appendChild(closeButton);
// ESCキーで終了
document.addEventListener('keydown', handleFullscreenKeydown);
// マウスポインターを非表示
document.body.style.cursor = 'none';
}
function exitFullscreen() {
isFullscreen = false;
(document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen).call(document);
// 編集コントロールを再表示
document.querySelectorAll('.control-point').forEach(point => {
point.classList.remove('hidden');
});
rotateHandle.classList.remove('hidden');
settingsPanel.classList.remove('hidden');
// フルスクリーン用クラスを削除
previewContainer.classList.remove('fullscreen-mode');
// 画像のドラッグを有効化
previewImage.style.cursor = 'move';
// 閉じるボタンを削除
const closeButton = document.querySelector('.close-fullscreen');
if (closeButton) closeButton.remove();
// マウスポインターを表示
document.body.style.cursor = '';
// イベントリスナーを削除
document.removeEventListener('keydown', handleFullscreenKeydown);
}
function handleFullscreenKeydown(e) {
if (e.key === 'Escape') {
exitFullscreen();
}
}
});
</script>
</body>
</html>