css/js 类似魔兽等游戏的技能冷却动画效果?

问题描述 投票:0回答:2

我正在尝试制作一种效果,当单击技能图标时,它会进入“冷却”状态,类似于《魔兽世界》等典型 MMO 中技能的冷却状态。

我找到了下面的代码来实现这样的效果,但它没有文档记录,我不太理解它,但想对其进行更改。

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); }
            });
        });
    });
});

我设法对其进行了一项我想要的更改,即添加带有冷却时间的文本,如下所示:(尚未在 js 中完成更新,但我认为这是我自己可以做的事情)

<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;
}

我需要帮助的其他更改是:

  • 逆时针填充而不是顺时针填充。

  • 翻转填充/未填充部分的不透明度(当前为 填充的部分是深色的,未填充的部分是浅色的,但我想要相反的)

javascript jquery css animation
2个回答
6
投票

另一种方法是将conic-gradient与一些CSS变量一起使用。这样我们就不需要构建 SVG。

PS:JS中有注释解释它是如何工作的。

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>


0
投票

如果你们允许我分享我的代码。它基于 @Reyno 的代码,但我使用 @keyframes 而不是 vanilla js。在我看来,它更好,代码也更简单。

请按照以下步骤:

const SECOND_IN_MS = 1000;
const UPDATE_INTERVAL = SECOND_IN_MS / 30; // frames per second

const startTimer = (target) => {
    // cooldown time
    const datasetTime = target.dataset.time;
    let time = parseInt(datasetTime);

    target.style = `--time: ${(datasetTime / SECOND_IN_MS)}s;`;

    // update remaining cooldown
    const intervalID = setInterval(() => {
        // 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 = '';

            clearInterval(intervalID);

            target.remove();
        }
    }, UPDATE_INTERVAL);
}

document.querySelector('#timer-creator').addEventListener('click', (event) => {

    const timerSecondsElement = document.querySelector("#timer-seconds");

    if (!timerSecondsElement.value.trim()) return;

    const divElement = document.createElement('div');
    divElement.classList.add('skill');
    divElement.dataset.time = timerSecondsElement.value * SECOND_IN_MS;

    timerSecondsElement.value = '';

    document.body.appendChild(divElement);

    startTimer(divElement);
})
@property --angle {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
}

.skill {
    position: relative;
    border: 1px solid #ddd;
    border-radius: 50%;
    width: 100px;
    height: 100px;
    overflow: hidden;
    cursor: pointer;
    display: flex;
    justify-items: center;
    align-items: center;
    justify-content: center;
    color: #555;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.skill::before {
    content: "";
    background: conic-gradient(rgba(0, 0, 0, 0.3) var(--angle), rgba(0, 0, 0, 0.1) var(--angle));
    animation: cooldown-animation var(--time) linear infinite;
    position: absolute;
    opacity: 0.9;
    top: 0;
    left: 0p;
    height: 100%;
    width: 100%;
}

@keyframes cooldown-animation {
    0% {
        --angle: 0deg;
    }

    100% {
        --angle: 360deg;
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Cooldown animation</title>
</head>
<body>
    <div>
        <input id="timer-seconds" type="text" placeholder="time in seconds..."/>
        <button id="timer-creator">Create timer</button>
    </div>
</body>
</html>

希望你喜欢。

干杯!

© www.soinside.com 2019 - 2024. All rights reserved.