Created
September 8, 2024 09:01
-
-
Save motsu0/3352a8905d7173bf33ce7248569fdd8b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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