Skip to content

Instantly share code, notes, and snippets.

@motsu0
Last active July 18, 2022 12:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save motsu0/a74aede87352ccc200035dd2d90ed8fe to your computer and use it in GitHub Desktop.
Save motsu0/a74aede87352ccc200035dd2d90ed8fe to your computer and use it in GitHub Desktop.
#quiz{
display: flex;
flex-direction: column;
box-sizing: border-box;
height: 90vh;
border: 2px solid #777;
background-color: #fff;
-webkit-tap-highlight-color: transparent;
}
.quiz__img-outer{
flex-grow: 1;
height: 0;
}
#quiz__img{
width: 100%;
height: 100%;
object-fit: contain;
filter: brightness(1);
cursor: pointer;
}
#quiz__img.s-black{
filter: brightness(0);
}
.quiz__control{
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding: 12px;
}
.quiz__bt{
padding: 8px 12px;
cursor: pointer;
}
#bt-fullscreen{
display: block;
padding: 4px 8px;
margin: 20px auto 100px auto;
cursor: pointer;
}
/* */
.settings{
padding: 8px 12px;
margin: 12px 0;
border: 2px solid #aaa;
}
.settings-row{
margin: 16px 0;
}
#input-file,#bt-rand{
cursor: pointer;
}
/* */
.preview-area{
padding: 8px 0;
border-top: 1px dashed #aaa;
}
.preview-area>summary{
display: inline-block;
cursor: pointer;
}
.preview-area>summary::before{
display: inline-block;
content: "+";
margin-right: 4px;
border: 1px solid #333;
line-height: 1em;
}
.preview-area[open]>summary::before{
content: "-";
}
#preview-box{
display: grid;
grid-template-columns: repeat(auto-fill,minmax(200px,1fr));
grid-template-rows: 200px;
grid-auto-rows: 200px;
row-gap: 12px;
column-gap: 12px;
margin-top: 8px;
}
.preview{
box-sizing: border-box;
position: relative;
border: 1px solid #FF6F00;
}
.preview__img{
box-sizing: border-box;
width: 100%;
height: 100%;
object-fit: contain;
padding: 8px;
cursor: pointer;
}
.bt-del{
padding: 2px 6px;
position: absolute;
top: 4px;
right: 4px;
border: none;
background-color: #e00000;
color: #fff;
cursor: pointer;
}
.preview__cursor-outer{
padding: 2px;
position: absolute;
right: 4px;
bottom: 4px;
border: 1px solid #777;
background-color: #fff;
cursor: move;
}
.preview__cursor-cover{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
.preview__cursor{
display: block;
width: 20px;
height: 20px;
position: relative;
z-index: 0;
}
/* */
.radio-area{
display: flex;
}
.radio-letter{
display: none;
}
.label-letter{
display: inline-block;
padding: 4px 8px;
margin: none;
border: 1px solid #777;
cursor: pointer;
}
.label-letter:first-of-type{
border-right: none;
}
.radio-letter:checked+.label-letter{
background-color: #FFECB3;
}
/* */
.s-hide{
display: none;
}
.s-invisible{
visibility: hidden;
}
.s-selected{
background-color: #FFE082;
}
.s-dragging,.s-target{
box-shadow: 0 0 0 3px #0000e0;
}
<!-- head -->
<link rel="stylesheet" href="pathto/now-loading.css">
<!-- /head -->
<h2>本体</h2>
<div id="quiz">
<div class="quiz__img-outer">
<img src="" alt="" id="quiz__img" class="s-invisible" draggable="false">
</div>
<div class="quiz__control">
<button id="bt-prev" class="quiz__bt" data-kana="まえのもんだい" data-kanji="前の問題">まえのもんだい</button>
<button id="bt-next" class="quiz__bt" data-kana="つぎのもんだい" data-kanji="次の問題">つぎのもんだい</button>
</div>
</div>
<button id="bt-fullscreen">全画面で遊ぶ</button>
<h2>設定</h2>
<div class="settings">
<div class="settings-row">
<input type="file" id="input-file" multiple>
<div id="message"></div>
</div>
<div class="settings-row">
<button id="bt-rand">ランダムに並び替える</button>
</div>
<details class="preview-area" open>
<summary>問題一覧</summary>
<div id="preview-box">
<div class="preview">
<img src="pathto/sample04.png" alt="" class="preview__img" draggable="false">
<button class="bt-del">×</button>
<div class="preview__cursor-outer">
<div class="preview__cursor-cover" draggable="true"></div>
<img src="pathto/cross.svg" alt="クロスカーソル" class="preview__cursor">
</div>
</div>
</div>
</details>
</div>
<div class="settings">
<div class="settings-row">
<div class="radio-area">
<input type="radio" name="radio-letter" id="radio-kana" class="radio-letter" checked>
<label for="radio-kana" class="label-letter">ひらがな</label>
<input type="radio" name="radio-letter" id="radio-kanji" class="radio-letter">
<label for="radio-kanji" class="label-letter">漢字</label>
</div>
</div>
</div>
<div id="preview-template" class="preview s-hide">
<img src="" alt="" class="preview__img" draggable="false">
<button class="bt-del">×</button>
<div class="preview__cursor-outer">
<div class="preview__cursor-cover" draggable="true"></div>
<img src="pathto/cross.svg" alt="クロスカーソル" class="preview__cursor">
</div>
</div>
<script src="pathto/now-loading.js"></script>
const el_quiz = document.getElementById('quiz');
const el_quiz__img = document.getElementById('quiz__img');
const bt_prev = document.getElementById('bt-prev');
const bt_next = document.getElementById('bt-next');
const bt_fullscreen = document.getElementById('bt-fullscreen');
const input_file = document.getElementById('input-file');
const el_message = document.getElementById('message')
const bt_rand = document.getElementById('bt-rand');
const el_preview_box = document.getElementById('preview-box');
const els_preview = el_preview_box.getElementsByClassName('preview');
const els_preview__img = el_preview_box.getElementsByClassName('preview__img');
const el_preview_template = document.getElementById('preview-template');
const els_radio_letter = document.getElementsByClassName('radio-letter');
const el_radio_kana = document.getElementById('radio-kana');
let el_dragging;
const nowloading = new nowLoading();
//アニメ準備
const anime = el_quiz__img.animate([
{filter: 'brightness(0)'},
{filter: 'brightness(1)'}
],{
duration: 1000,
fill: 'forwards'
});
anime.pause();
anime.onfinish = ()=>{
el_quiz__img.classList.remove('s-black');
};
el_quiz__img.onload = ()=>{
el_quiz__img.classList.remove('s-invisible');
};
el_quiz__img.addEventListener('click',toggleBlack);
//リスナ
bt_prev.addEventListener('click',()=>{changeImg('prev')});
bt_next.addEventListener('click',()=>{changeImg('next')});
input_file.addEventListener('change',checkFile);
[...els_radio_letter].forEach(el=>{
el.addEventListener('change',changeLetter);
});
bt_rand.addEventListener('click',shufflePreview);
bt_fullscreen.addEventListener('click',toFullScreen);
//デモ処理
initPreview(els_preview[0]);
displayImg(els_preview__img[0]);
//ここから関数
function checkFile(e){
nowloading.start();
const files = e.target.files;
const first = (els_preview.length==0);
el_message.textContent = '';
if(files.length==0){
nowloading.stop();
el_message.textContent = 'ファイルを読み込めませんでした。';
return
}
const promise_set = [];
[...files].forEach(file=>{
if(file.type.indexOf('image')==-1){
return;
}
loadFile(file);
});
Promise.all(promise_set).then(()=>{
if(first){
displayImg(els_preview__img[0]);
}
nowloading.stop();
e.target.value = '';
});
function loadFile(file){
const promise = new Promise(solve=>{
const reader = new FileReader();
reader.onload = ev=>{
const el_clone = el_preview_template.cloneNode(true);
el_clone.id = '';
el_clone.classList.remove('s-hide');
initPreview(el_clone);
//
const el_img = el_clone.getElementsByClassName('preview__img')[0];
el_img.src = ev.target.result;
//
el_preview_box.appendChild(el_clone);
solve();
}
reader.readAsDataURL(file);
});
promise_set.push(promise);
}
}
function initPreview(el_clone){
//リスナ登録
const el_img = el_clone.getElementsByClassName('preview__img')[0];
el_img.addEventListener('click',selectImg);
const bt_del = el_clone.getElementsByClassName('bt-del')[0];
bt_del.addEventListener('click',()=>{
deletePreview(el_clone,el_img);
});
//ドラッグアンドロップ用処理
const el_cursor = el_clone.getElementsByClassName('preview__cursor-cover')[0];
el_cursor.addEventListener('dragstart',e=>{
dragStart(e,el_clone);
});
el_cursor.addEventListener('dragend',e=>{
dragEnd(e,el_clone);
});
//受け側
el_clone.addEventListener('dragover',dragOver);
el_clone.addEventListener('dragleave',dragLeave);
el_clone.addEventListener('drop',drop);
}
function selectImg(e){
anime.cancel();
[...els_preview__img].forEach(el=>{
el.classList.remove('s-selected');
});
//
displayImg(e.currentTarget);
}
function changeImg(type){
if(els_preview__img.length==0) return;
const index_old = (()=>{
for(let i=0;i<els_preview__img.length;i++){
if(els_preview__img[i].classList.contains('s-selected')){
els_preview__img[i].classList.remove('s-selected');
return i;
}
}
return false;
})();
if(index_old===false) return;
//
const diff = (()=>{
if(type=='prev'){
return -1;
}else{
return 1;
}
})();
const index_new = (()=>{
let temp = index_old + diff;
if(temp<0){
temp = els_preview__img.length-1;
}else if(temp>=els_preview__img.length){
temp = 0
}
return temp;
})();
//
anime.cancel();
const el = els_preview__img[index_new];
displayImg(el);
}
function displayImg(el_img){
el_img.classList.add('s-selected');
el_quiz__img.classList.add('s-invisible');
el_quiz__img.classList.add('s-black');
el_quiz__img.src = el_img.src;
}
function toggleBlack(e){
const el = e.currentTarget;
if(el.classList.contains('s-black')){
if(anime.playState!='running') anime.play();
}else{
anime.cancel();
el.classList.add('s-black');
}
}
function shufflePreview(){
if(els_preview__img.length<2) return;
shuffleCollection(els_preview,el_preview_box);
anime.cancel();
[...els_preview__img].forEach(el=>{
el.classList.remove('s-selected');
});
displayImg(els_preview__img[0]);
}
function deletePreview(el_clone,el_img){
if(el_img.classList.contains('s-selected')){
const index = [...els_preview__img].indexOf(el_img);
el_clone.remove();
anime.cancel();
el_quiz__img.src = '';
if(els_preview__img.length>0){
if(els_preview__img[index]==undefined){
displayImg(els_preview__img[0]);
}else{
displayImg(els_preview__img[index]);
}
}else{
el_quiz__img.classList.add('s-invisible');
}
}else{
el_clone.remove();
}
}
function dragStart(e,el){
const x_offset = (el.clientWidth - 4) - (e.target.clientWidth - e.offsetX);
const y_offset = el.clientHeight - 4 - (e.target.clientHeight - e.offsetY);
e.dataTransfer.setDragImage(el,x_offset,y_offset);
e.dataTransfer.setData('text','d'); //スマホ用に必要
el_dragging = el;
setTimeout(()=>{
el.classList.add('s-dragging');
},1);
}
function dragOver(e){
if(el_dragging!=undefined){
e.preventDefault();
e.currentTarget.classList.add('s-target');
}
}
function dragLeave(e){
e.currentTarget.classList.remove('s-target');
}
function drop(e){
if(el_dragging==undefined) return;
const el = e.currentTarget;
const dummy = document.createElement('span');
dummy.style.display = 'none';
el_preview_box.insertBefore(dummy,el_dragging);
el_preview_box.insertBefore(el_dragging,el);
el_preview_box.insertBefore(el,dummy);
dummy.remove();
el.classList.remove('s-target');
el_dragging.classList.remove('s-dragging');
el_dragging = undefined;
}
function dragEnd(e,el){
el_dragging = undefined;
el.classList.remove('s-dragging');
}
function toFullScreen(){
el_quiz.requestFullscreen();
}
function changeLetter(){
if(el_radio_kana.checked){
bt_prev.textContent = bt_prev.dataset.kana;
bt_next.textContent = bt_next.dataset.kana;
}else{
bt_prev.textContent = bt_prev.dataset.kanji;
bt_next.textContent = bt_next.dataset.kanji;
}
}
function randomMN(m,n){
const d = Math.max(m,n)-Math.min(m,n);
return Math.floor(Math.random()*(d+1)+Math.min(m,n));
}
function shuffleCollection(collection,parent){
const array = (new Array(collection.length)).fill(0).map((v,i)=>i);
for(let i=array.length-1;i>=1;i--){
const j = randomMN(0,i);
[array[j],array[i]] = [array[i],array[j]];
}
const els = new Array(collection.length);
array.forEach((v,i)=>{
els[i] = collection[v];
});
els.forEach(el=>{
parent.appendChild(el);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment