Skip to content

Instantly share code, notes, and snippets.

@motsu0
Last active May 12, 2025 09:56
Show Gist options
  • Select an option

  • Save motsu0/3352a8905d7173bf33ce7248569fdd8b to your computer and use it in GitHub Desktop.

Select an option

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 {
display: flex;
justify-content: center;
user-select: none;
}
#slide-wrapper {
display: flex;
position: relative;
}
#draft-image {
border: 1px solid #9e9e9e;
}
#slideA {
width: 50%;
z-index: 2;
}
#slideB {
z-index: 1;
}
#slidebar {
box-sizing: border-box;
width: 4px;
height: 100%;
position: absolute;
top: 0;
z-index: 3;
border: #fff solid;
border-width: 0 1px;
background-color: #616161;
cursor: col-resize;
}
#slide-controller {
margin-inline: auto;
}
#slide-range {
font-size: 16px;
width: calc(100% + 16px);
height: 44px;
margin-left: -8px;
}
.slide-btn-container {
display: flex;
justify-content: space-between;
}
.slide-btn {
width: 44px;
height: 44px;
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="slide-wrapper">
<img src="" alt="" id="draft-image" draggable="false" />
<div id="slidebar"></div>
</div>
</div>
<div id="slide-controller">
<div>
<input type="range" id="slide-range" step="" />
</div>
<div class="slide-btn-container">
<button id="slide-left-btn" class="slide-btn">←</button>
<button id="slide-right-btn" class="slide-btn">→</button>
</div>
</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 = <HTMLDivElement>(
// document.getElementById('draft-image-container')
// );
const draftImage = document.getElementById('draft-image');
const slidebar = document.getElementById('slidebar');
const slideController = document.getElementById('slide-controller');
const slideRange = document.getElementById('slide-range');
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);
slideRange.addEventListener('input', slideRangeMove);
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) {
var _a;
const outerW = slideContainer.clientWidth;
const outerH =
(((_a = window.visualViewport) === null || _a === void 0
? void 0
: _a.height) || window.innerHeight) * 0.85;
const imgW = img.naturalWidth;
const imgH = img.naturalHeight;
const outerRate = outerH / outerW;
const imgRate = imgH / imgW;
let newWidth = 1;
if (outerRate >= imgRate) {
//width合わせ
newWidth = outerW;
const new_h = (outerW / imgW) * imgH;
img.style.width = outerW + 'px';
img.style.height = new_h + 'px';
slideController.style.width = ``;
} else {
//height合わせ
newWidth = (outerH / imgH) * imgW;
img.style.width = newWidth + 'px';
img.style.height = outerH + 'px';
slideController.style.width = `${newWidth}px`;
}
slidebarX = Math.round(newWidth / 2);
slidebar.style.transform = `translateX(${slidebarX}px)`;
slideRange.max = `${newWidth}`;
slideRange.value = `${slidebarX}`;
drawImage();
}
//
function slidebarMoveStart() {
IsSlidebarMoving = true;
}
function slidebarMove(e) {
if (!IsSlidebarMoving) return;
const d =
e.pageX -
(window.scrollX + draftImage.getBoundingClientRect().left);
const x = (() => {
if (d < 0) {
return 0;
} else if (d > draftImage.clientWidth) {
return draftImage.clientWidth;
} else {
return d;
}
})();
slidebarX = x;
slidebar.style.transform = `translateX(${slidebarX}px)`;
slideRange.value = `${slidebarX}`;
drawImage();
}
function slidebarMoveEnd() {
IsSlidebarMoving = false;
}
function slideRangeMove(e) {
const target = e.target;
slidebarX = Number(target.value);
slidebar.style.transform = `translateX(${slidebarX}px)`;
drawImage();
}
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)`;
slideRange.value = `${slidebarX}`;
drawImage();
}, 10);
}
function slideEnd() {
clearInterval(slidetimer);
}
function drawImage() {
const actualRate = draftImage.naturalWidth / draftImage.clientWidth;
const leftImageWidth = Math.round(slidebarX * 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