Таймер обратного отсчета с круглым progress bar
Я создал таймер обратного отсчета. У меня получился бордюр, который я сделал круглым. Поскольку таймер стремится к нулю, круглая граница должна менять цвет с уменьшением в секундах
Я создал JSFIDDLE
var displayminutes;
var displayseconds;
var initializeTimer = 1.5 // enter in minutes
var minutesToSeconds = initializeTimer*60;
$("#document").ready(function(){
setTime = getTime();
$(".btn-timer").html(setTime[0]+":"+setTime[1])
});
$(".btn-timer").click(function(){
var startCountDownTimer = setInterval(function(){
minutesToSeconds = minutesToSeconds-1;
var timer = getTime();
$(".btn-timer").html(timer[0]+":"+timer[1]);
if(minutesToSeconds == 0){
clearInterval(startCountDownTimer);
console.log("completed");
}
},1000)
});
function getTime(){
displayminutes = Math.floor(minutesToSeconds/60);
displayseconds = minutesToSeconds - (displayminutes*60);
if(displayseconds < 10)
{
displayseconds ="0"+displayseconds;
}
if(displayminutes < 10)
{
displayminutes = "0"+displayminutes;
}
return [displayminutes, displayseconds];
}
body{
background-color:#ebebeb;
margin : 0 auto;
padding: 0 auto;
}
.outer{
border-radius: 50%;
height:115px;
width:115px;
border:13px solid #e0e0e0;
}
.btn-timer{
border-radius: 50%;
position:relative;
height:115px;
width:115px;
border:none !important;
font-size:25px;
background: rgba(255,255,255,1);
background: -moz-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(246,246,246,1) 47%, rgba(237,237,237,1) 100%);
background: -webkit-gradient(left top, left bottom, color-stop(0%, rgba(255,255,255,1)), color-stop(47%, rgba(246,246,246,1)), color-stop(100%, rgba(237,237,237,1)));
background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(246,246,246,1) 47%, rgba(237,237,237,1) 100%);
background: -o-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(246,246,246,1) 47%, rgba(237,237,237,1) 100%);
background: -ms-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(246,246,246,1) 47%, rgba(237,237,237,1) 100%);
background: linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(246,246,246,1) 47%, rgba(237,237,237,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed', GradientType=0 );
-webkit-box-shadow: 3px 3px 5px 0px rgba(0,0,0,0.75);
-moz-box-shadow: 3px 3px 5px 0px rgba(0,0,0,0.75);
box-shadow: 3px 3px 5px 0px rgba(0,0,0,0.75);
}
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div class="outer">
<button class="btn btn-default btn-timer">0.00</button>
</div>
Как получить круглый индикатор выполнения. Я искал плагин jQuery, но он не соответствует моим требованиям. Я ищу результат, похожий на эту ссылку
Свободный перевод вопроса https://stackoverflow.com/q/31198304/7394871 от участника @Alaksandar Jesus Gene.
Ответы (3 шт):
Ниже приведен пример таймера обратного отсчета с круглым индикатором выполнения, который меняет цвет по мере уменьшения значения.
В основном мы делаем следующее: (дополнительные сведения см. Во встроенных комментариях кода)
- 4 дополнительных div абсолютно спозиционированы поверх родительского. Каждый представляет квадрант окружности.
- изначально угол наклона всех из них равен 0 градусов, поэтому все они полностью видны и покрывают весь родительский элемент. Это скрывает тень родительского блока и, таким образом, делает его похожим на сплошной круг.
- На каждой итерации мы изменяем угол наклона каждого квадранта (div) таким образом, чтобы квадранты в конечном итоге становились невидимыми один за другим, открывая тем самым прямоугольную тень родительского элемента.
- Квадранты становятся невидимыми, когда угол наклона достигает +/- 90 градусов, поэтому на каждой итерации угол вычисляется как (90 градусов / количество шагов, пройденных в этом квадранте).
- По мере того, как прогресс перемещается от одного квадранта к другому, тень родительского блока изменяется, чтобы создать вид индикатора выполнения, меняющего свой цвет.
- Исходный CodePen использует значение атрибута
data-progressнепосредственно как содержимое псевдоэлемента. Но это значение увеличивается с каждой итерацией. Поскольку он также используется при вычислении угловskew, я оставил его как есть и использовал отдельное поле для таймера обратного отсчета. Содержимое псевдоэлементов не может быть установлено с помощью JS, поэтому я добавил еще один div для текста таймера.
window.onload = function() {
var progressbar = document.querySelector('div[data-progress]'),
quad1 = document.querySelector('.quad1'),
quad2 = document.querySelector('.quad2'),
quad3 = document.querySelector('.quad3'),
quad4 = document.querySelector('.quad4'),
counter = document.querySelector('.counter');
var progInc = setInterval(incrementProg, 1000); // вызывать функцию каждую секунду
function incrementProg() {
progress = progressbar.getAttribute('data-progress'); //получить текущее значение
progress++; // увеличивать значение индикатора выполнения на 1 с каждой итерацией
progressbar.setAttribute('data-progress', progress); //установить значение атрибута
counter.textContent = 100 - parseInt(progress, 10); // установить значение таймера обратного отсчета
setPie(progress); // вызвать функцию индикатора выполнения рисования на основе значения прогресса
if (progress == 100) {
clearInterval(progInc); // сбросить таймер, когда обратный отсчет завершится
}
}
function setPie(progress) {
/* Если прогресс меньше 25, измените угол наклона первого квадранта.t */
if (progress <= 25) {
quad1.setAttribute('style', 'transform: skew(' + progress * (-90 / 25) + 'deg)');
}
/* От 25 до 50: скрыть 1-й квадрант + изменить угол перекоса 2-го квадранта */
else if (progress > 25 && progress <= 50) {
quad1.setAttribute('style', 'transform: skew(-90deg)'); // полностью скрывает 1-й квадрант
quad2.setAttribute('style', 'transform: skewY(' + (progress - 25) * (90 / 25) + 'deg)');
progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px orange');
}
/* От 50 до 75: скрыть первые 2 квадранта + изменить угол наклона 3-го квадранта. */
else if (progress > 50 && progress <= 75) {
quad1.setAttribute('style', 'transform: skew(-90deg)'); // полностью скрывает 1-й
quad2.setAttribute('style', 'transform: skewY(90deg)'); // полностью скрывает второй
quad3.setAttribute('style', 'transform: skew(' + (progress - 50) * (-90 / 25) + 'deg)');
progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px yellow');
}
/* Аналогично приведенному выше для значения от 75 до 100 */
else if (progress > 75 && progress <= 100) {
quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely
quad2.setAttribute('style', 'transform: skewY(90deg)'); // hides 2nd completely
quad3.setAttribute('style', 'transform: skew(-90deg)'); // hides 3rd completely
quad4.setAttribute('style', 'transform: skewY(' + (progress - 75) * (90 / 25) + 'deg)');
progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px green');
}
}
}
div[data-progress] {
box-sizing: border-box;
position: relative;
height: 200px;
width: 200px;
background: beige;
border-radius: 50%;
box-shadow: inset 0px 0px 0px 20px red;
transition: all 1s;
overflow: hidden;
}
.counter {
position: absolute;
height: 100%;
width: 100%;
top: 0%;
left: 0%;
text-align: center;
line-height: 200px;
border-radius: 50%;
background: transparent;
z-index: 4;
}
div > div {
position: absolute;
height: 50%;
width: 50%;
background: inherit;
border-radius: 0%;
}
.quad1,
.quad2 {
left: 50%;
transform-origin: left bottom;
}
.quad3,
.quad4 {
left: 0%;
transform-origin: right top;
}
.quad1,
.quad4 {
top: 0%;
}
.quad2,
.quad3 {
top: 50%;
}
.quad1,
.quad3 {
transform: skew(0deg); /* invisible at -90deg */
}
.quad2,
.quad4 {
transform: skewY(0deg); /* invisible at 90deg */
}
/* Just for demo */
body {
background: linear-gradient(90deg, crimson, indianred, purple);
}
div[data-progress] {
margin: 40px auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div data-progress="0">
<div class="quad1"></div>
<div class="quad2"></div>
<div class="quad3"></div>
<div class="quad4"></div>
<div class='counter'>100</div>
</div>
В приведенном ниже фрагменте я добавил другой фон для каждого квадранта, чтобы обеспечить лучшую визуальную иллюстрацию того, что именно происходит.
window.onload = function() {
var progressbar = document.querySelector('div[data-progress]'),
quad1 = document.querySelector('.quad1'),
quad2 = document.querySelector('.quad2'),
quad3 = document.querySelector('.quad3'),
quad4 = document.querySelector('.quad4'),
counter = document.querySelector('.counter');
var progInc = setInterval(incrementProg, 1000); // call function every second
function incrementProg() {
progress = progressbar.getAttribute('data-progress'); //get current value
progress++; // increment the progress bar value by 1 with every iteration
progressbar.setAttribute('data-progress', progress); //set value to attribute
counter.textContent = 100 - parseInt(progress, 10); // set countdown timer's value
setPie(progress); // call the paint progress bar function based on progress value
if (progress == 100) {
clearInterval(progInc); // clear timer when countdown is complete
}
}
function setPie(progress) {
/* If progress is less than 25, modify skew angle the first quadrant */
if (progress <= 25) {
quad1.setAttribute('style', 'transform: skew(' + progress * (-90 / 25) + 'deg)');
}
/* Between 25-50, hide 1st quadrant + modify skew angle of 2nd quadrant */
else if (progress > 25 && progress <= 50) {
quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely
quad2.setAttribute('style', 'transform: skewY(' + (progress - 25) * (90 / 25) + 'deg)');
progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px orange');
}
/* Between 50-75, hide first 2 quadrants + modify skew angle of 3rd quadrant */
else if (progress > 50 && progress <= 75) {
quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely
quad2.setAttribute('style', 'transform: skewY(90deg)'); // hides 2nd completely
quad3.setAttribute('style', 'transform: skew(' + (progress - 50) * (-90 / 25) + 'deg)');
progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px yellow');
}
/* Similar to above for value between 75-100 */
else if (progress > 75 && progress <= 100) {
quad1.setAttribute('style', 'transform: skew(-90deg)'); // hides 1st completely
quad2.setAttribute('style', 'transform: skewY(90deg)'); // hides 2nd completely
quad3.setAttribute('style', 'transform: skew(-90deg)'); // hides 3rd completely
quad4.setAttribute('style', 'transform: skewY(' + (progress - 75) * (90 / 25) + 'deg)');
progressbar.setAttribute('style', 'box-shadow: inset 0px 0px 0px 20px green');
}
}
}
div[data-progress] {
box-sizing: border-box;
position: relative;
height: 200px;
width: 200px;
background: beige;
border-radius: 50%;
box-shadow: inset 0px 0px 0px 20px red;
transition: all 1s;
overflow: hidden;
}
.counter {
position: absolute;
height: 100%;
width: 100%;
top: 0%;
left: 0%;
text-align: center;
line-height: 200px;
border-radius: 50%;
background: transparent;
z-index: 4;
}
div > div {
position: absolute;
height: 50%;
width: 50%;
border-radius: 0%;
}
.quad1,
.quad2 {
left: 50%;
transform-origin: left bottom;
}
.quad3,
.quad4 {
left: 0%;
transform-origin: right top;
}
.quad1, .quad4{
top: 0%;
}
.quad2, .quad3{
top: 50%;
}
.quad1, .quad3{
transform: skew(0deg);
}
.quad2, .quad4{
transform: skewY(0deg);
}
/* Just for demo */
body {
background: linear-gradient(90deg, crimson, indianred, purple);
}
div[data-progress] {
margin: 40px auto;
}
.quad1 {
background: blue;
}
.quad2 {
background: pink;
}
.quad3 {
background: tan;
}
.quad4 {
background: teal;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div data-progress="0">
<div class="quad1"></div>
<div class="quad2"></div>
<div class="quad3"></div>
<div class="quad4"></div>
<div class='counter'>100</div>
</div>
С развитием браузеров и появлением новых технологий (таких, как CSS-переменные и conic-gradient), можно существенно сократить код, но при этом, расширить функциональность и улучшить визуальную составляющую:
let oCount = document.querySelector("div[countdown]");
oCount.innerText = oCount.countValue = +oCount.getAttribute("countdown");
oCount.countRatio = 360 / oCount.countValue;
oCount.countColor = 100 / oCount.countValue;
oCount.countLight = oCount.countValue / 20;
oCount.countTimer = setInterval(fCountdown.bind(oCount), 1000);
function fCountdown() {
let nCount = this.countValue;
if (nCount > 0) {
nCount--;
this.innerText = this.countValue = nCount;
this.style.setProperty("--deg", 361 - nCount * this.countRatio);
this.style.setProperty("--col", `hsla(${nCount * this.countColor}, 100%, ${50 - nCount / this.countLight}%, 1)`);
} else {
clearInterval(this.countTimer);
console.log("Complete " + this.getAttribute('countdown'));
}
}
body { display: flex; justify-content: space-around; align-items: center; height: 100vh; margin: 0; background-image: radial-gradient(#aef, #000); }
div[countdown] {
--deg: -1;
--col: hsla(0, 100%, 50%, 1);
height: 120px; width: 120px;
border-radius: 50%;
text-align: center;
font: bold 36px/120px monospace;
background-image: radial-gradient(#fff 49px, #f000 51px), conic-gradient(var(--col) calc(var(--deg) * 1deg - 1deg), transparent calc(var(--deg) * 1deg + 1deg)), radial-gradient(#fff3 40px, #4441 60px);
box-shadow: inset 0 0 10px -5px #000a;
}
<div countdown="60"></div>
Если необходимо использовать несколько подобных блоков, то достаточно добавить в скрипт пару строк:
[...document.querySelectorAll("div[countdown]")].forEach(function(oCount) {
oCount.innerText = oCount.countValue = +oCount.getAttribute("countdown");
oCount.countRatio = 360 / oCount.countValue;
oCount.countColor = 100 / oCount.countValue;
oCount.countLight = oCount.countValue / 20;
oCount.countTimer = setInterval(fCountdown.bind(oCount), 100);
});
function fCountdown() {
let nCount = this.countValue;
if (nCount > 0) {
nCount--;
this.innerText = this.countValue = nCount;
this.style.setProperty("--deg", 361 - nCount * this.countRatio);
this.style.setProperty("--col", `hsla(${nCount * this.countColor}, 100%, ${50 - nCount / this.countLight}%, 1)`);
} else {
clearInterval(this.countTimer);
console.log("Complete " + this.getAttribute('countdown'));
}
}
body { display: flex; justify-content: space-around; align-items: center; height: 100vh; margin: 0; background-image: radial-gradient(#aef, #000); }
div[countdown] {
--deg: -1;
--col: hsla(0, 100%, 50%, 1);
height: 120px; width: 120px;
border-radius: 50%;
text-align: center;
font: bold 36px/120px monospace;
background-image: radial-gradient(#fff 49px, #f000 51px), conic-gradient(var(--col) calc(var(--deg) * 1deg - 1deg), transparent calc(var(--deg) * 1deg + 1deg)), radial-gradient(#fff3 40px, #4441 60px);
box-shadow: inset 0 0 10px -5px #000a;
}
<div countdown="100"></div><div countdown="50"></div><div countdown="75"></div>
Добавляем ещё несколько строк в JS - теперь запуск происходит по клику на блоке:
[...document.querySelectorAll("div[countdown]")].forEach(function(oCount) {
oCount.innerText = oCount.countValue = +oCount.getAttribute("countdown");
oCount.countRatio = 360 / oCount.countValue;
oCount.countColor = 100 / oCount.countValue;
oCount.countLight = oCount.countValue / 20;
oCount.addEventListener("click", function() {
if (!this.countTimer || this.countTimer === "undefined") {
this.innerText = this.countValue = +this.getAttribute("countdown");
this.countTimer = setInterval(fCountdown.bind(this), 100);
}
});
});
function fCountdown() {
let nCount = this.countValue;
if (nCount > 0) {
nCount--;
this.innerText = this.countValue = nCount;
this.style.setProperty("--deg", 361 - nCount * this.countRatio);
this.style.setProperty("--col", `hsla(${nCount * this.countColor}, 100%, ${50 - nCount / this.countLight}%, 1)`);
} else {
clearInterval(this.countTimer);
delete this.countTimer;
console.log("Complete " + this.getAttribute("countdown"));
}
}
body { display: flex; justify-content: space-around; align-items: center; height: 100vh; margin: 0; background-image: radial-gradient(#aef, #000); }
div[countdown] {
--deg: -1;
--col: hsla(0, 100%, 50%, 1);
height: 120px; width: 120px;
border-radius: 50%;
text-align: center;
font: bold 36px/120px monospace;
background-image: radial-gradient(#fff 49px, #f000 51px), conic-gradient(var(--col) calc(var(--deg) * 1deg - 1deg), transparent calc(var(--deg) * 1deg + 1deg)), radial-gradient(#fff3 40px, #4441 60px);
box-shadow: inset 0 0 10px -5px #000a;
}
<div countdown="100"></div><div countdown="50"></div><div countdown="75"></div>
И напоследок - если нужна прозрачность блока, тогда переносим градиент в псевдоэлемент, применяем к нему свойство clip-path с SVG-маской и располагаем его позади блока:
[...document.querySelectorAll("div[countdown]")].forEach(function(oCount) {
oCount.innerText = oCount.countValue = +oCount.getAttribute("countdown");
oCount.countRatio = 360 / oCount.countValue;
oCount.countColor = 100 / oCount.countValue;
oCount.countLight = oCount.countValue / 20;
oCount.addEventListener("click", function() {
if (!this.countTimer || this.countTimer === "undefined") {
this.innerText = this.countValue = +this.getAttribute("countdown");
this.countTimer = setInterval(fCountdown.bind(this), 100);
}
});
});
function fCountdown() {
let nCount = this.countValue;
if (nCount > 0) {
nCount--;
this.innerText = this.countValue = nCount;
this.style.setProperty("--deg", 361 - nCount * this.countRatio);
this.style.setProperty("--col", `hsla(${nCount * this.countColor}, 100%, ${50 - nCount / this.countLight}%, 1)`);
} else {
clearInterval(this.countTimer);
delete this.countTimer;
console.log("Complete " + this.getAttribute("countdown"));
}
}
body { display: flex; justify-content: space-around; align-items: center; height: 100vh; margin: 0; background-image: radial-gradient(#aef, #000); }
div[countdown] {
--deg: 360;
--col: hsla(0, 100%, 50%, 0);
position: relative;
height: 120px;
width: 120px;
border-radius: 50%;
font: bold 36px/120px monospace;
text-align: center;
user-select: none;
cursor: pointer;
box-shadow: inset 0 0 10px -5px #000a;
overflow: hidden;
}
div[countdown]::before {
content: attr(countdown);
position: absolute;
top:0; left:0;
z-index: -1;
height: inherit; width: inherit;
background-image: conic-gradient(var(--col) calc(var(--deg) * 1deg - 1deg), transparent calc(var(--deg) * 1deg + 1deg)), radial-gradient(#fff3 40px, #4441 60px);
clip-path: url(#ring-clip-path);
}
.svg { position: absolute; height: 0; width: 0; }
<div countdown="100"></div><div countdown="50"></div><div countdown="75"></div>
<svg class="svg">
<defs>
<clipPath id="ring-clip-path">
<path d="M 0 0 L 60 0 L 60 10 C 30 10 10 30 10 60 C 10 90 30 110 60 110 C 90 110 110 90 110 60 C 110 30 90 10 60 10 L 60 0 L 120 0 L 120 120 L 0 120 Z"></path>
</clipPath>
</defs>
</svg>
Вот мой вариант, который никому не нужен ^-^
var timer = setInterval(timerFunction, 1000),
time = 31,
procent = 100 / time,
per = 0;
const timerBlock = document.querySelector(".timer");
function timerFunction() {
per += procent;
//Hour
hour = Math.floor(time / 3600);
hour = hour < 10 ? "0" + hour : hour;
//Minutes
minute = Math.floor((time - hour * 3600) / 60);
minute = minute < 10 ? "0" + minute : minute;
minute = minute > 59 ? "00" : minute;
//Seconds
second = time % 60;
second = second < 10 ? "0" + second : second;
timerBlock.innerHTML = hour + ":" + minute + ":" + second;
time--;
timerBlock.style.cssText =
"background: conic-gradient(hsla(180, 100%, 50%, 0.5) " + per +
"%, transparent 0 " + (100 - per) + "%)";
if (time == -1) clearInterval(timer);
}
body {
width: 100%;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
}
.timer {
width: 240px;
height: 240px;
box-shadow: 0 0 15px 4px rgba(0, 0, 0, .4);
background-color: #fff;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 48px;
font-family: consolas;
font-weight: 700;
}
<div class="timer"></div>