I'm trying to make an effect where when a skill icon is clicked, it goes on "cooldown" similar to when a skill goes on cooldown in a typical MMO like WoW.
I found the below code for achieving such an effect, but it's not documented and I don't very much understand it but want to make changes to it.
html:
<table>
<tr>
<td>
<div class="skill"></div>
</td>
<td>
<div class="skill"></div>
</td>
<td>
<div class="skill"></div>
</td>
<td>
<div class="skill"></div>
</td>
<td>
<div class="skill"></div>
</td>
</tr>
</table>
css:
.skill {
margin: 0px;
padding: 0px;
position: relative;
border: 1px solid #36393E;
border-radius: 5%;
width: 44px;
height: 44px;
overflow: hidden;
background-color: transparent;
background-image: url("skill.png");
background-repeat: no-repeat;
background-size: 100%;
}
.cooldown {
position: absolute;
opacity: 0.8;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
}
js:
function cooldown(container, percent) {
var div = $(container);
div.empty();
var total = 100;
if(percent < total) {
var data = [percent, total - percent];
var width = div.width();
var height = div.height();
var cx = width / 2;
var cy = height / 2;
var r = cx * Math.SQRT2;
var colors = [null, '#AAA'];
var svgns = "http://www.w3.org/2000/svg";
var chart = document.createElementNS(svgns, "svg:svg");
chart.setAttribute("width", width);
chart.setAttribute("height", height);
chart.setAttribute("viewBox", "0 0 " + width + " " + height);
var angles = []
for(var i = 0; i < data.length; i++) angles[i] = data[i] / total * Math.PI * 2;
startangle = 0;
for(var i = 0; i < data.length; i++) {
var endangle = startangle + angles[i];
var x1 = cx + r * Math.sin(startangle);
var y1 = cy - r * Math.cos(startangle);
var x2 = cx + r * Math.sin(endangle);
var y2 = cy - r * Math.cos(endangle);
var big = 0;
if (endangle - startangle > Math.PI) big = 1;
var path = document.createElementNS(svgns, "path");
var d = "M " + cx + "," + cy + " L " + x1 + "," + y1 +
" A " + r + "," + r + " 0 " + big + " 1 " +
x2 + "," + y2 + " Z";
path.setAttribute("d", d);
if(colors[i]) {
path.setAttribute("fill", colors[i]);
} else {
path.setAttribute("opacity", 0);
}
chart.appendChild(path);
startangle = endangle;
}
chart.setAttribute('overflow', 'hidden');
div.append(chart);
}
}
$(window).ready(function() {
$('.skill').each(function() {
var skill = $(this);
var div = $('<div />').appendTo(skill).addClass('cooldown');
var radius = div.parent().width() / 2;
skill.click(function() {
$({pct: 0}).animate({pct: 100}, {
duration: 5000,
step: function (curLeft) { cooldown(div, curLeft); }
});
});
});
});
I managed to make one change to it that I wanted which was to add text with the cooldown time, like so: (haven't done updating of it in js yet but I think that's something I can do myself)
<div class="skill"><div class="text">5</div></div>
.skill .text {
font-size: 14px;
color: #E1E5EB;
text-shadow: 1px 1px 1px #000000;
line-height: 44px;
text-align: center;
background-color: transparent;
}
The other changes that I need help with are:
Filling counter-clockwise instead of clockwise.
Flip the opacity of filled/unfilled portions (currently the filled portion is dark and unfilled is light but I want the opposite)
Another approach would be to use the conic-gradient together with some CSS variables. This way we don't need to build an SVG.
PS: There are comments in the JS to explain how it works.
const SECOND_IN_MS = 1000;
const UPDATE_INTERVAL = SECOND_IN_MS / 60; // Update 60 times per second (60 FPS)
const SKILL_CLASS = 'skill';
const DISABLED_CLASS = 'disabled';
// Cooldowns per skill in milliseconds
const COOLDOWN_MAP = new Map([
['run', 1000],
['jump', 2000],
['crawl', 3000],
['slide', 4000],
['tumble', 5000],
]);
// Get skills table from the DOM
const skillsTable = document.querySelector('.skills-table');
// Activate clicked skill
const activateSkill = (event) => {
const {target} = event;
// Exit if we click on anything that isn't a skill
if(!target.classList.contains(SKILL_CLASS)) return;
target.classList.add(DISABLED_CLASS);
target.style = '--time-left: 100%';
// Get cooldown time
const skill = target.dataset.skill;
let time = COOLDOWN_MAP.get(skill) - UPDATE_INTERVAL;
// Update remaining cooldown
const intervalID = setInterval(() => {
// Pass remaining time in percentage to CSS
const passedTime = time / COOLDOWN_MAP.get(skill) * 100;
target.style = `--time-left: ${passedTime}%`;
// Display time left
target.textContent = (time / SECOND_IN_MS).toFixed(2);
time -= UPDATE_INTERVAL;
// Stop timer when there is no time left
if(time < 0) {
target.textContent = '';
target.style = '';
target.classList.remove(DISABLED_CLASS);
clearInterval(intervalID);
}
}, UPDATE_INTERVAL);
}
// Add click handler to the table
skillsTable.addEventListener('click', activateSkill, false);
.skill {
position: relative;
border: 1px solid #36393E;
border-radius: 5%;
width: 44px;
height: 44px;
overflow: hidden;
cursor: pointer;
}
/* Prevents you from clicked the button multiple times */
.skill.disabled {
pointer-events: none;
}
/* Makes sure we click the skill not anything in it */
.skill > * {
pointer-events: none;
}
.skill::before {
content: "";
background: conic-gradient(
rgba(0, 0, 0, 0.7) var(--time-left),
rgba(0, 0, 0, 0.1) var(--time-left));
position: absolute;
opacity: 0.8;
top: 0;
left: 0p;
height: 100%;
width: 100%;
}
<table class='skills-table'>
<tr>
<td>
<!-- data-skill refers to the COOLDOWN_MAP in JS -->
<div class="skill" data-skill='run'></div>
</td>
<td>
<!-- data-skill refers to the COOLDOWN_MAP in JS -->
<div class="skill" data-skill='jump'></div>
</td>
<td>
<!-- data-skill refers to the COOLDOWN_MAP in JS -->
<div class="skill" data-skill='crawl'></div>
</td>
<td>
<!-- data-skill refers to the COOLDOWN_MAP in JS -->
<div class="skill" data-skill='slide'></div>
</td>
<td>
<!-- data-skill refers to the COOLDOWN_MAP in JS -->
<div class="skill" data-skill='tumble'></div>
</td>
</tr>
</table>