Last active
February 19, 2023 10:24
-
-
Save motsu0/b1486bd23782908c8b5a3bb21d517b5c 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
.block{ | |
margin: 8px 0 32px 0; | |
} | |
.bt-control{ | |
display: block; | |
margin: 12px 0; | |
cursor: pointer; | |
} | |
.message{ | |
margin: 12px 0; | |
color: #e00000; | |
} | |
/* */ | |
#input-file{ | |
display: none; | |
} | |
#bt-file-input{ | |
display: block; | |
width: 80%; | |
height: 60px; | |
font-size: 1.2rem; | |
cursor: pointer; | |
} | |
#bt-file-input.s-dragover{ | |
background-color: #ddd; | |
} | |
/* */ | |
.bt-del{ | |
display: block; | |
margin: 0 auto; | |
border: none; | |
background-color: #e00000; | |
color: #fff; | |
cursor: pointer; | |
} | |
.bt-del:hover{ | |
background-color: #d00000; | |
} | |
/* */ | |
.settings__row{ | |
padding: 8px; | |
border: 1px dashed #555; | |
} | |
.settings__row:nth-of-type(n+2){ | |
border-top: none; | |
} | |
.input-number{ | |
width: 100px; | |
height: 20px; | |
margin: 4px; | |
} | |
.settings__radio-group{ | |
color: #999; | |
} | |
.settings__radio-group:nth-of-type(n+2){ | |
margin-top: 4px; | |
} | |
.radio{ | |
vertical-align: middle; | |
} | |
.radio:checked+.label-radio{ | |
color: #222; | |
} | |
.radio:checked~.radio-text{ | |
color: #222; | |
} | |
.radio,.label-radio{ | |
cursor: pointer; | |
} | |
.label-radio[for="radio-jpg"]{ | |
margin-right: 20px; | |
} | |
#input-quality{ | |
width: 40px; | |
height: 20px; | |
margin-left: 4px; | |
} | |
#input-name-plus{ | |
width: 100px; | |
height: 20px; | |
} | |
#bt-submit{ | |
padding: 8px 16px; | |
margin-top: 24px; | |
cursor: pointer; | |
} | |
/* */ | |
.area-table{ | |
overflow-x: auto; | |
} | |
.table-file{ | |
border-collapse: collapse; | |
} | |
.table-file__thead{ | |
background-color: #C8E6C9; | |
text-align: center; | |
} | |
.table-file__td{ | |
box-sizing: border-box; | |
border: 1px solid #777; | |
padding: 2px 8px; | |
word-break: keep-all; | |
white-space: nowrap; | |
} | |
.table-file__td--right{ | |
text-align: right; | |
} | |
/* */ | |
.s-hide{ | |
display: none; | |
} | |
.t-red{ | |
color: #e00000; | |
} |
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
<h3>入力</h3> | |
<div class="block"> | |
<input type="file" id="input-file" multiple> | |
<button id="bt-file-input">画像を読み込む</button> | |
<p> | |
※あまりファイル数が多すぎると固まるので注意。<br> | |
最大20枚程度を推奨。 | |
</p> | |
</div> | |
<h3>ファイルリスト</h3> | |
<div class="block"> | |
<div class="control"> | |
<button id="bt-clear" class="bt-control">入力ファイルを全て消去</button> | |
</div> | |
<div id="message-input" class="message s-hide">いくつかの画像の読込に失敗しました。</div> | |
<div class="area-table"> | |
<table class="table-file"> | |
<thead class="table-file__thead"> | |
<tr> | |
<td class="table-file__td">削除</td> | |
<td class="table-file__td">ファイル名</td> | |
<td class="table-file__td">容量</td> | |
</tr> | |
</thead> | |
<tbody id="tbody-file-input"></tbody> | |
</table> | |
</div> | |
</div> | |
<h4>出力設定</h4> | |
<div class="block"> | |
<div class="settings"> | |
<div class="settings__row"> | |
最大横幅(width) :<input type="number" id="input-width" class="input-number" value="">px<br> | |
最大縦幅(height):<input type="number" id="input-height" class="input-number" value="">px | |
</div> | |
<div class="settings__row"> | |
<div class="settings__radio-group"> | |
<input type="radio" name="extension" value="jpg" id="radio-jpg" class="radio" checked> | |
<label for="radio-jpg" class="label-radio">.jpg</label> | |
<span class="radio-text">画質(0-100):<input type="number" id="input-quality" placeholder="80" value="80" min="0" max="100"></span> | |
</div> | |
<div class="settings__radio-group"> | |
<input type="radio" name="extension" value="png" id="radio-png" class="radio"> | |
<label for="radio-png" class="label-radio">.png</label> | |
</div> | |
</div> | |
<div class="settings__row"> | |
出力ファイル名 = 入力ファイル名 + <input type="text" id="input-name-plus" value="-resized" placeholder="-resized"> + 拡張子 | |
</div> | |
</div> | |
<button id="bt-submit">変換する</button> | |
</div> | |
<h3>出力</h3> | |
<div class="block"> | |
<div class="control"> | |
<button id="bt-dl-all" class="bt-control">全て保存</button> | |
<button id="bt-dl-zip" class="bt-control">zipにまとめて保存</button> | |
</div> | |
<div id="message-output" class="message s-hide"></div> | |
<div class="area-table"> | |
<table class="table-file"> | |
<thead class="table-file__thead"> | |
<tr> | |
<td class="table-file__td"></td> | |
<td class="table-file__td">ファイル名</td> | |
<td class="table-file__td">元容量</td> | |
<td class="table-file__td">新容量</td> | |
<td class="table-file__td">圧縮率</td> | |
<td class="table-file__td">元解像度</td> | |
<td class="table-file__td">新解像度</td> | |
</tr> | |
</thead> | |
<tbody id="tbody-file-output"></tbody> | |
</table> | |
</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
const nowloading = new nowLoading(); | |
nowloading.start(); | |
const el_input_file = document.getElementById('input-file'); | |
const bt_file_input = document.getElementById('bt-file-input'); | |
const bt_clear = document.getElementById('bt-clear'); | |
const el_message_input = document.getElementById('message-input'); | |
const els_tr_file_input = document.getElementsByClassName('tr-file-input'); | |
const el_tbody_file_input = document.getElementById('tbody-file-input'); | |
const el_input_width = document.getElementById('input-width'); | |
const el_input_height = document.getElementById('input-height'); | |
const el_radio_jpg = document.getElementById('radio-jpg'); | |
const el_input_quality = document.getElementById('input-quality'); | |
const el_input_name_plus = document.getElementById('input-name-plus'); | |
const bt_submit = document.getElementById('bt-submit'); | |
const bt_dl_all = document.getElementById('bt-dl-all'); | |
const bt_dl_zip = document.getElementById('bt-dl-zip'); | |
const el_message_output = document.getElementById('message-output'); | |
const el_tbody_file_output = document.getElementById('tbody-file-output'); | |
const els_a_download = document.getElementsByClassName('a-download'); | |
let array_URL = []; | |
let list_files_input = []; | |
let list_files_output = []; | |
init(); | |
nowloading.stop(); | |
function init(){ | |
bt_file_input.addEventListener('click',()=>{ | |
el_input_file.click(); | |
}); | |
//ボタンへのdragenter | |
bt_file_input.addEventListener('dragenter',()=>{ | |
bt_file_input.classList.add('s-dragover'); | |
}); | |
//ボタンへのdragover | |
bt_file_input.addEventListener('dragover',e=>{ | |
e.preventDefault(); | |
}); | |
//ボタンからのdragleave | |
bt_file_input.addEventListener('dragleave',()=>{ | |
bt_file_input.classList.remove('s-dragover'); | |
}); | |
//ボタンへのdrop => ファイル処理 | |
bt_file_input.addEventListener('drop',e=>{ | |
e.preventDefault(); | |
bt_file_input.classList.remove('s-dragover'); | |
const files = e.dataTransfer.files; | |
nowloading.start(); | |
setTimeout(()=>{ | |
fileCheck(files); | |
},1); | |
}); | |
//ボタンクリック => ファイル処理 | |
el_input_file.addEventListener('change',e=>{ | |
const files = e.target.files; | |
nowloading.start(); | |
setTimeout(()=>{ | |
fileCheck(files); | |
},1); | |
}); | |
// | |
bt_clear.addEventListener('click',fileClear); | |
bt_submit.addEventListener('click',()=>{ | |
nowloading.start(); | |
setTimeout(()=>{ | |
fileChange(); | |
},1); | |
}); | |
bt_dl_all.addEventListener('click',downloadAll); | |
bt_dl_zip.addEventListener('click',downloadZip); | |
} | |
function fileCheck(files){ | |
if(files.length===0){ | |
nowloading.stop(); | |
return | |
} | |
const files_img = [...files].filter(file=>file.type.includes('image')); | |
el_input_file.value = ''; | |
if(files_img.length===0){ | |
nowloading.stop(); | |
return | |
} | |
fileLoad(files_img); | |
} | |
function fileLoad(files){ | |
const array_promise = []; | |
let is_img_onerror = false; | |
el_message_input.classList.add('s-hide'); | |
files.forEach(file=>{ | |
//img処理 | |
const promise = new Promise((resolve,reject)=>{ | |
const img = new Image(); | |
img.onload = ()=>{ | |
resolve({ | |
file: file, | |
element: img | |
}); | |
}; | |
img.onerror = ()=>{ | |
is_img_onerror = true; | |
resolve(); | |
} | |
const reader = new FileReader(); | |
reader.onload = ev=>{ | |
img.src = ev.target.result; | |
} | |
reader.readAsDataURL(file); | |
}); | |
array_promise.push(promise); | |
}); | |
Promise.all(array_promise) | |
.then(values=>{ | |
if(is_img_onerror){ | |
console.error('some imgs load failure'); | |
el_message_input.classList.remove('s-hide'); | |
}else{ | |
console.log('all imgs loaded'); | |
} | |
values.forEach(obj_val=>{ | |
if(obj_val===undefined) return; | |
const file = obj_val.file; | |
list_files_input.push({ | |
name: file.name, | |
size_old: file.size, | |
element: obj_val.element | |
}); | |
//dom処理 | |
const el_tr = document.createElement('tr'); | |
el_tr.classList.add('tr-file-input'); | |
//td 削除 | |
const el_td_del = document.createElement('td'); | |
el_td_del.classList.add('table-file__td'); | |
const bt_del = document.createElement('button'); | |
bt_del.classList.add('bt-del'); | |
bt_del.textContent = '✕'; | |
bt_del.addEventListener('click',()=>{ | |
fileDel(el_tr); | |
}); | |
el_td_del.appendChild(bt_del); | |
el_tr.appendChild(el_td_del); | |
//td ファイル名 | |
const el_td_name = document.createElement('td'); | |
el_td_name.classList.add('table-file__td'); | |
el_td_name.classList.add('table-file__td--name'); | |
el_td_name.textContent = file.name; | |
el_tr.appendChild(el_td_name); | |
//td 容量 | |
const el_td_size_old = document.createElement('td'); | |
el_td_size_old.classList.add('table-file__td'); | |
el_td_size_old.classList.add('table-file__td--right'); | |
const str_size = sizeToStr(file.size); | |
el_td_size_old.textContent = str_size; | |
el_tr.appendChild(el_td_size_old); | |
//tr 追加 | |
el_tbody_file_input.appendChild(el_tr); | |
}); | |
nowloading.stop(); | |
}); | |
} | |
function fileClear(){ | |
el_tbody_file_input.textContent = ''; | |
list_files_input = []; | |
el_message_input.classList.add('s-hide'); | |
} | |
function fileDel(el_tr){ | |
const index = [...els_tr_file_input].indexOf(el_tr); | |
list_files_input = list_files_input.filter((v,i)=>i!==index); | |
el_tr.remove(); | |
el_message_output.classList.add('s-hide'); | |
} | |
function fileChange(){ | |
el_message_output.classList.add('s-hide'); | |
if(list_files_input.length===0){ | |
nowloading.stop(); | |
return; | |
} | |
el_tbody_file_output.textContent = ''; | |
list_files_output = []; | |
array_URL.forEach(url=>{ | |
window.URL.revokeObjectURL(url); | |
}); | |
array_URL = []; | |
//共通値取得 | |
const width_max = getWH(Number(el_input_width.value)); | |
const height_max = getWH(Number(el_input_height.value)); | |
const extension = (()=>{ | |
if(el_radio_jpg.checked){ | |
return '.jpg'; | |
}else{ | |
return '.png'; | |
} | |
})(); | |
const quality = (()=>{ | |
let temp = Number(el_input_quality.value); | |
if(temp<0){ | |
temp = 0; | |
el_input_quality.value = 0; | |
} | |
if(temp>100){ | |
temp = 100; | |
el_input_quality.value = 100; | |
} | |
return temp/100; | |
})(); | |
const type_img = (()=>{ | |
if(el_radio_jpg.checked){ | |
return 'image/jpeg'; | |
}else{ | |
return 'image/png'; | |
} | |
})(); | |
const array_promise = []; | |
list_files_input.forEach(file=>{ | |
//縮小割合算出 | |
const img = file.element; | |
const width_old = img.naturalWidth; | |
const height_old = img.naturalHeight; | |
const rate_w = calcRate(width_old, width_max); | |
const rate_h = calcRate(height_old, height_max); | |
const rate = Math.min(rate_w,rate_h); | |
const width_new = (()=>{ | |
const temp = Math.floor(width_old*rate); | |
if(temp===0) return 1; | |
return temp; | |
})(); | |
const height_new = (()=>{ | |
const temp = Math.floor(height_old*rate); | |
if(temp===0) return 1; | |
return temp; | |
})(); | |
//canvas初期化 => draw | |
const canvas = document.createElement('canvas'); | |
canvas.width = width_new; | |
canvas.height = height_new; | |
const ctx = canvas.getContext('2d'); | |
ctx.drawImage(img,0,0,width_new,height_new); | |
//dom処理 | |
const el_tr = document.createElement('tr'); | |
//td 保存 | |
const el_td_download = document.createElement('td'); | |
el_td_download.classList.add('table-file__td'); | |
const el_a = document.createElement('a'); | |
const match_name = file.name.match(/(.*)\..*/); | |
const name_new = match_name[1] + el_input_name_plus.value + extension; | |
file.name_new = name_new; | |
el_a.classList.add('a-download'); | |
el_a.download = name_new; | |
el_td_download.appendChild(el_a); | |
el_tr.appendChild(el_td_download); | |
//td ファイル名 | |
const el_td_name = document.createElement('td'); | |
el_td_name.classList.add('table-file__td'); | |
el_td_name.textContent = name_new; | |
el_tr.appendChild(el_td_name); | |
//td 元容量 | |
const el_td_size_old = document.createElement('td'); | |
el_td_size_old.classList.add('table-file__td'); | |
el_td_size_old.classList.add('table-file__td--right'); | |
const str_size_old = sizeToStr(file.size_old); | |
el_td_size_old.textContent = str_size_old; | |
el_tr.appendChild(el_td_size_old); | |
//td 新容量 | |
const el_td_size_new = document.createElement('td'); | |
el_td_size_new.classList.add('table-file__td'); | |
el_td_size_new.classList.add('table-file__td--right'); | |
el_tr.appendChild(el_td_size_new); | |
//td 圧縮率 | |
const el_td_compress = document.createElement('td'); | |
el_td_compress.classList.add('table-file__td'); | |
el_td_compress.classList.add('table-file__td--right'); | |
el_tr.appendChild(el_td_compress); | |
//td 元解像度 | |
const el_td_wh_old = document.createElement('td'); | |
el_td_wh_old.classList.add('table-file__td'); | |
el_td_wh_old.classList.add('table-file__td--right'); | |
el_td_wh_old.textContent = width_old+'×'+height_old; | |
el_tr.appendChild(el_td_wh_old); | |
//td 新解像度 | |
const el_td_wh_new = document.createElement('td'); | |
el_td_wh_new.classList.add('table-file__td'); | |
el_td_wh_new.classList.add('table-file__td--right'); | |
el_td_wh_new.textContent = width_new+'×'+height_new; | |
el_tr.appendChild(el_td_wh_new); | |
// | |
el_tbody_file_output.appendChild(el_tr); | |
//canvas => a 処理 | |
const promise = new Promise(resolve=>{ | |
if(el_radio_jpg.checked){ | |
canvas.toBlob(blob=>{ | |
fileChangeSub(file, blob, el_a, el_td_size_new, el_td_compress); | |
resolve(); | |
},'image/jpeg',quality); | |
}else{ | |
canvas.toBlob(blob=>{ | |
fileChangeSub(file, blob, el_a, el_td_size_new, el_td_compress); | |
resolve(); | |
},'image/png'); | |
} | |
}); | |
array_promise.push(promise); | |
}); | |
Promise.all(array_promise) | |
.then(()=>{ | |
console.log('all imgs changed'); | |
nowloading.stop(); | |
}); | |
function fileChangeSub(file, blob, el_a, el_td_size_new, el_td_compress){ | |
//dlリンク処理 | |
const url_img = window.URL.createObjectURL(blob); | |
el_a.href = url_img; | |
el_a.textContent = '保存'; | |
array_URL.push(url_img); | |
//zip用処理 | |
list_files_output.push({ | |
name: file.name_new, | |
blob: blob | |
}); | |
//dom処理 | |
el_td_size_new.textContent = sizeToStr(blob.size); | |
el_td_compress.textContent = calcCompress(file.size_old, blob.size); | |
} | |
} | |
function downloadAll(){ | |
nowloading.start(); | |
[...els_a_download].forEach(el_a=>{ | |
el_a.click(); | |
}); | |
nowloading.stop(); | |
} | |
function downloadZip(){ | |
if(list_files_output.length===0) return; | |
nowloading.start(); | |
const zip = new JSZip(); | |
list_files_output.forEach(file=>{ | |
zip.file(file.name, file.blob); | |
}); | |
zip.generateAsync({type: 'blob'}) | |
.then(blob=>{ | |
const url = window.URL.createObjectURL(blob); | |
const el_a = document.createElement('a'); | |
el_a.download = 'resized.zip'; | |
el_a.href = url; | |
el_a.click(); | |
window.URL.revokeObjectURL(url); | |
nowloading.stop(); | |
}); | |
} | |
function sizeToStr(size){ | |
if(size<1024) return size+'B'; | |
if(size<1024*1024){ | |
const size_kb = (size / 1024).toFixed(1); | |
return size_kb+'KB'; | |
} | |
const size_mb = (size / (1024*1024)).toFixed(1); | |
return size_mb+'MB'; | |
} | |
function getWH(value){ | |
if(!Number.isInteger(value)) return undefined; | |
if(value<1) return undefined; | |
return value; | |
} | |
function calcRate(val_old, val_new){ | |
if(val_new===undefined) return 1; | |
if(val_new/val_old>1) return 1; | |
return val_new / val_old; | |
} | |
function calcCompress(val_old, val_new){ | |
const val = Math.round(((val_old-val_new)/val_old)*1000)/10; | |
if(val%1===0) return val + '.0%'; | |
return val + '%'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment