Skip to content

Instantly share code, notes, and snippets.

@motsu0
Last active August 7, 2022 13:13
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/a6e3807cf385ff923575cc68d2d00748 to your computer and use it in GitHub Desktop.
Save motsu0/a6e3807cf385ff923575cc68d2d00748 to your computer and use it in GitHub Desktop.
.sandglass{
box-sizing: border-box;
width: 90%;
max-width: 400px;
margin: 100px auto;
position: relative;
user-select: none;
}
.sandglass::after{
content: "";
display: block;
padding-top: 100%;
}
#sandglass-inner{
box-sizing: border-box;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
transition: transform 1s;
}
.sandglass__half{
height: 50%;
position: relative;
background-color: rgb(217, 255, 255);
}
.sandglass__top{
clip-path: polygon(0 0, 100% 0, 51% 100%, 49% 100%);
}
.sandglass__bottom{
clip-path: polygon(49% 0, 51% 0, 100% 100%, 0 100%);
}
.sandglass__top-wrapper,.sandglass__bottom-wrapper{
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
.sandglass__bottom-wrapper{
transform: rotateZ(180deg);
}
.sandglass__sand{
width: 100%;
height: 0;
position: absolute;
left: 0;
bottom: 0;
z-index: 0;
background-color: #DEB887;
}
#sandglass-inner.s-rotated .sandglass__sand{
top: 0;
}
#sandglass__particle{
width: 8px;
height: 8px;
position: absolute;
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-4px,-4px);
background-color: #DEB887;
}
/* */
.settings{
margin-top: 40px;
text-align: center;
}
.settings-row{
margin: 24px 0;
}
#input-time{
width: 48px;
height: 20px;
margin-right: 4px;
}
/* */
.s-hide{
display: none;
}
.t-pointer{
cursor: pointer;
}
<div class="sandglass">
<div id="sandglass-inner" class="t-pointer">
<div class="sandglass__half sandglass__top">
<img src="pathto/sandglass-top.svg" alt="砂時計の半分の縁" class="sandglass__top-wrapper">
<div id="sandglass__top__sand" class="sandglass__sand"></div>
</div>
<div class="sandglass__half sandglass__bottom">
<img src="pathto/sandglass-top.svg" alt="砂時計の半分の縁" class="sandglass__bottom-wrapper">
<div id="sandglass__bottom__sand" class="sandglass__sand"></div>
</div>
</div>
<div id="sandglass__particle" class="s-hide"></div>
</div>
<div class="settings">
<div class="settings-row">
<input id="input-time" class="t-pointer" type="number" min="1" max="3600" placeholder="3" value="3">
</div>
<div class="settings-row">
<button id="bt-rotate" class="t-pointer">ひっくり返す</button>
</div>
<div class="settings-row">
<button id="bt-reset" class="t-pointer">リセット</button>
</div>
<div class="settings-row">
<input id="input-color" class="t-pointer" type="color" value="#DEB887">
</div>
</div>
const el_sandglass_inner = document.getElementById('sandglass-inner');
const el_sandglass__sand = document.getElementsByClassName('sandglass__sand');
const el_sandglass__top__sand = document.getElementById('sandglass__top__sand');
const el_sandglass__bottom__sand = document.getElementById('sandglass__bottom__sand');
const el_sandglass__particle = document.getElementById('sandglass__particle');
const input_time = document.getElementById('input-time');
const bt_rotate = document.getElementById('bt-rotate');
const bt_reset = document.getElementById('bt-reset');
const input_color = document.getElementById('input-color');
const sand_max = 90;
const sand = [0,100];
let timer_sand;
let time_max;
let time_remain = 0;
let timer_fall;
el_sandglass_inner.addEventListener('click',checkForm);
bt_rotate.addEventListener('click',checkForm);
bt_reset.addEventListener('click',reset);
input_color.addEventListener('input',changeColor);
const anime_rotate = el_sandglass_inner.animate([
{
transform: 'rotateZ(0)'
},
{
transform: 'rotateZ(180deg)'
}
],{
duration: 1000,
fill: "forwards"
});
anime_rotate.pause();
anime_rotate.playbackRate = -1;
const height_sandglass = el_sandglass_inner.clientHeight;
const anime_fall = el_sandglass__particle.animate([
{
transform: 'translate(-4px,-4px)'
},
{
transform: `translate(-4px,${height_sandglass/2-8-height_sandglass*.01}px)`
}
],{
duration: 500,
iterations: Infinity
});
anime_fall.pause();
anime_rotate.onfinish = ()=>{
anime_fall.currentTime = 0;
el_sandglass__particle.classList.remove('s-hide');
anime_fall.play();
};
viewSand();
function checkForm(){
const minute = Number(input_time.value);
if(minute===0) return;
time_max = 60*minute;
rotate();
}
function rotate(){
clearInterval(timer_sand);
el_sandglass__particle.classList.add('s-hide');
anime_fall.pause();
input_time.disabled = true;
el_sandglass_inner.classList.toggle('s-rotated');
const is_nospin = !el_sandglass_inner.classList.contains('s-rotated');
const diff = 100/time_max;
time_remain = time_max - time_remain;
viewSand();
timer_sand = setInterval(()=>{
if(time_remain>=1) time_remain--;
if(is_nospin){
sand[0] -= diff;
sand[1] += diff;
}else{
sand[0] += diff;
sand[1] -= diff;
}
sand.forEach((v,i)=>{
if(v<0) sand[i] = 0;
if(v>100) sand[i] = 100;
});
viewSand();
if(time_remain===0){
clearInterval(timer_sand);
el_sandglass__particle.classList.add('s-hide');
anime_fall.pause();
input_time.disabled = false;
}
},1000);
anime_rotate.playbackRate = -anime_rotate.playbackRate;
anime_rotate.play();
}
function viewSand(){
const is_nospin = !el_sandglass_inner.classList.contains('s-rotated');
const height_sand = [0,0];
if(is_nospin){
height_sand[0] = sand_max*Math.sqrt((sand[0]*sand_max/100)/100);
height_sand[1] = 100 - 100*Math.sqrt(((100-sand_max)+sand_max*(100-sand[1])/100)/100)+1;
}else{
height_sand[0] = 100 - 100*Math.sqrt(((100-sand_max)+sand_max*(100-sand[0])/100)/100)+1;
height_sand[1] = sand_max*Math.sqrt((sand[1]*sand_max/100)/100);
}
el_sandglass__top__sand.style.height = height_sand[0]+'%';
el_sandglass__bottom__sand.style.height = height_sand[1]+'%';
}
function reset(){
clearInterval(timer_sand);
el_sandglass__particle.classList.add('s-hide');
anime_fall.pause();
sand[0] = 0;
sand[1] = 100;
time_remain = 0;
input_time.disabled = false;
el_sandglass_inner.classList.remove('s-rotated');
anime_rotate.cancel();
anime_rotate.playbackRate = -1;
anime_fall.cancel();
viewSand();
}
function changeColor(){
const color = input_color.value;
[...el_sandglass__sand].forEach(el=>{
el.style.backgroundColor = color;
});
el_sandglass__particle.style.backgroundColor = color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment