Skip to content

Instantly share code, notes, and snippets.

@motsu0
Created September 8, 2024 09:01
Show Gist options
  • Save motsu0/3352a8905d7173bf33ce7248569fdd8b to your computer and use it in GitHub Desktop.
Save motsu0/3352a8905d7173bf33ce7248569fdd8b to your computer and use it in GitHub Desktop.
.file-input {
display: block;
margin: 16px 0;
}
#message-box {
padding-left: 12px;
color: red;
}
#preview-image {
width: 100px;
}
/* */
#slide-container {
box-sizing: border-box;
height: 85vh;
position: relative;
border: 1px solid #9e9e9e;
user-select: none;
}
.slide-unit {
display: flex;
align-items: center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
background-color: #fff;
}
#slideA {
width: 50%;
z-index: 2;
}
#slideB {
z-index: 1;
}
#slidebar {
box-sizing: border-box;
width: 4px;
height: 100%;
position: absolute;
z-index: 3;
border: #fff solid;
border-width: 0 1px;
background-color: #616161;
cursor: col-resize;
}
.slide-btn {
position: absolute;
bottom: 4px;
z-index: 5;
cursor: pointer;
user-select: none;
}
#slide-left-btn {
left: 4px;
}
#slide-right-btn {
right: 4px;
}
/* */
.result {
display: flex;
flex-direction: column;
row-gap: 20px;
}
.result__row {
display: flex;
flex-direction: column;
row-gap: 8px;
align-items: flex-start;
}
.canvas {
object-fit: contain;
width: 100%;
max-width: 500px;
height: 300px;
}
<h2>概要</h2>
<p>1枚の画像から、間に鏡を置いたような左右対称の画像を作成するツール。</p>
<h2>本体</h2>
<h3>画像読み込み</h3>
<input type="file" id="file-input" />
<div id="message-box"></div>
<div class="preview-box">
<img src="" alt="" id="preview-image" />
</div>
<h3>分割点設定</h3>
<div id="slide-container">
<div id="draft-image-container" class="slide-unit">
<img src="" alt="" id="draft-image" draggable="false" />
</div>
<div id="slidebar"></div>
<button id="slide-left-btn" class="slide-btn">←</button>
<button id="slide-right-btn" class="slide-btn">→</button>
</div>
<h3>結果</h3>
<div class="result">
<div class="result__row">
<canvas id="canvas1" class="canvas"></canvas>
<button id="dl-btn1">保存</button>
</div>
<div class="result__row">
<canvas id="canvas2" class="canvas"></canvas>
<button id="dl-btn2">保存</button>
</div>
</div>
'use strict';
(() => {
// @ts-ignore
const nowloading = new nowLoading();
nowloading.start();
//画像読み込み
const fileInput = document.getElementById('file-input');
const messageBox = document.getElementById('message-box');
const previewImage = document.getElementById('preview-image');
fileInput.addEventListener('change', (e) => {
checkFile(fileInput.files);
});
//スライド比較
const slideContainer = document.getElementById('slide-container');
const draftImageContainer = document.getElementById(
'draft-image-container'
);
const draftImage = document.getElementById('draft-image');
const slidebar = document.getElementById('slidebar');
let IsSlidebarMoving = false;
let slidebarX;
draftImage.onload = () => {
resizeImage(draftImage);
};
slidebar.style.transform = `translateX(${
slideContainer.clientWidth / 2
}px)`;
slidebar.addEventListener('pointerdown', slidebarMoveStart);
slideContainer.addEventListener('pointermove', slidebarMove);
slideContainer.addEventListener('pointerup', slidebarMoveEnd);
slideContainer.addEventListener('pointerleave', slidebarMoveEnd);
const slideLeftBtn = document.getElementById('slide-left-btn');
const slideRightBtn = document.getElementById('slide-right-btn');
slideLeftBtn.addEventListener('pointerdown', () => {
slideStart(-1);
});
slideLeftBtn.addEventListener('pointerup', () => {
slideEnd();
});
slideLeftBtn.addEventListener('pointerleave', () => {
slideEnd();
});
slideRightBtn.addEventListener('pointerdown', () => {
slideStart(1);
});
slideRightBtn.addEventListener('pointerup', () => {
slideEnd();
});
slideRightBtn.addEventListener('pointerleave', () => {
slideEnd();
});
const canvas1 = document.getElementById('canvas1');
const ctx1 = canvas1.getContext('2d');
const canvas2 = document.getElementById('canvas2');
const ctx2 = canvas2.getContext('2d');
const dlBtn1 = document.getElementById('dl-btn1');
const dlBtn2 = document.getElementById('dl-btn2');
dlBtn1.addEventListener('click', () => {
downloadImage(0);
});
dlBtn2.addEventListener('click', () => {
downloadImage(1);
});
//サンプル画像読み込み
draftImage.src = '../img/sample01.jpg';
nowloading.stop();
//関数
function checkFile(files) {
messageBox.textContent = '';
if (files === null) return;
if (files.length == 0) {
return;
}
previewImage.src = '';
if (files[0].type.indexOf('image') == -1) {
fileInput.value = '';
messageBox.textContent = '画像ファイルを選択してください。';
return;
}
const reader = new FileReader();
reader.onload = (ev) => {
const src = reader.result;
previewImage.src = src;
draftImage.src = src;
};
reader.readAsDataURL(files[0]);
}
function resizeImage(img) {
const outerW = slideContainer.clientWidth;
const outerH = slideContainer.clientHeight;
const imgW = img.naturalWidth;
const imgH = img.naturalHeight;
const outerRate = outerH / outerW;
const imgRate = imgH / imgW;
if (outerRate >= imgRate) {
//width合わせ
const new_h = (outerW / imgW) * imgH;
img.style.width = outerW + 'px';
img.style.height = new_h + 'px';
img.style.transform = '';
} else {
//height合わせ
const new_w = (outerH / imgH) * imgW;
img.style.width = new_w + 'px';
img.style.height = outerH + 'px';
img.style.transform = `translateX(${(outerW - new_w) / 2}px)`;
}
slidebarX = outerW / 2;
slidebar.style.transform = `translateX(${slidebarX}px)`;
drawImage();
}
//
function slidebarMoveStart() {
IsSlidebarMoving = true;
}
function slidebarMove(e) {
if (!IsSlidebarMoving) return;
const d =
e.pageX -
(window.scrollX + slideContainer.getBoundingClientRect().left);
const x = (() => {
if (d < 0) {
return 0;
} else if (d > slideContainer.clientWidth) {
return slideContainer.clientWidth;
} else {
return d;
}
})();
slidebarX = x;
slidebar.style.transform = `translateX(${slidebarX}px)`;
drawImage();
}
function slidebarMoveEnd() {
IsSlidebarMoving = false;
}
let slidetimer;
function slideStart(diff) {
slidetimer = setInterval(() => {
const d = slidebarX + diff;
const x = (() => {
if (d < 0) {
return 0;
} else if (d > slideContainer.clientWidth) {
return slideContainer.clientWidth;
} else {
return d;
}
})();
slidebarX = x;
slidebar.style.transform = `translateX(${slidebarX}px)`;
drawImage();
}, 10);
}
function slideEnd() {
clearInterval(slidetimer);
}
function drawImage() {
const actualX =
(draftImageContainer.clientWidth - draftImage.clientWidth) / 2;
const actualRate = draftImage.naturalWidth / draftImage.clientWidth;
const leftImageWidth = Math.round(
(slidebarX - actualX) * actualRate
);
canvas1.width = leftImageWidth * 2;
canvas1.height = draftImage.naturalHeight;
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
ctx1.save();
ctx1.drawImage(
draftImage,
0,
0,
leftImageWidth,
draftImage.naturalHeight,
0,
0,
leftImageWidth,
canvas1.height
);
ctx1.scale(-1, 1);
ctx1.drawImage(
draftImage,
0,
0,
leftImageWidth,
draftImage.naturalHeight,
-leftImageWidth * 2,
0,
leftImageWidth,
canvas1.height
);
ctx1.restore();
//
const rightImageWidth = draftImage.naturalWidth - leftImageWidth;
canvas2.width = rightImageWidth * 2;
canvas2.height = draftImage.naturalHeight;
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
ctx2.save();
ctx2.drawImage(
draftImage,
leftImageWidth,
0,
rightImageWidth,
draftImage.naturalHeight,
rightImageWidth,
0,
rightImageWidth,
canvas2.height
);
ctx2.scale(-1, 1);
ctx2.drawImage(
draftImage,
leftImageWidth,
0,
rightImageWidth,
draftImage.naturalHeight,
-rightImageWidth,
0,
rightImageWidth,
canvas2.height
);
ctx2.restore();
}
function downloadImage(canvasId) {
nowloading.start();
const canvases = [canvas1, canvas2];
canvases[canvasId].toBlob((blob) => {
if (blob === null) {
nowloading.stop();
return;
}
const alink = document.createElement('a');
alink.download = 'download.png';
alink.href = URL.createObjectURL(blob);
alink.click();
URL.revokeObjectURL(alink.href);
nowloading.stop();
}, 'image/png');
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment