طراحی تایمر معکوس با HTML ،CSS و JavaScript — به زبان ساده
آیا تاکنون لازم شده است یک تایمر شمارش معکوس در یک پروژه قرار دهید؟ در چنین مواردی معمولاً به دنبال یک افزونه میگردیم، اما پیادهسازی چنین تایمری کاری سرراست محسوب میشود و صرفاً به آشنایی با HTML ،CSS و JavaScript نیاز دارد. در این مقاله با مراحل طراحی تایمر معکوس با HTML ،CSS و JavaScript آشنا خواهیم شد. در نهایت چیزی مانند زیر به دست میآوریم:
کارهایی که تایمر ما انجام میدهد و در این نوشته بررسی خواهیم کرد، به شرح زیر هستند:
- نمایش زمان باقیمانده اولیه
- تبدیل مقدار زمان به قالب MM:SS.
- محاسبه تفاضل بین زمان باقیمانده اولیه و مقدار زمان سپریشده.
- تغییر دادن رنگ در زمان نزدیک شدن زمان باقیمانده به صفر.
- نمایش پیشروی زمان باقیمانده به صورت یک حلقه انیمیت شده.
اینها مواردی هستند که باید پیادهسازی کنیم. در بخش بعدی کار را آغاز میکنیم.
گام 1: آغاز کار با markup ابتدایی و استایلها
کار خود را با ایجاد یک قالب مقدماتی برای تایمر خود آغاز میکنیم. ابتدا یک svg میسازیم و یک عنصر circle درون آن اضافه میکنیم تا یک حلقه تایمر رسم کنیم که زمان سپریشده را نشان میدهد و یک span برای نمایش مقدار زمان باقیمانده نمایش دهیم. توجه کنید که مشغول نوشتن HTML در جاوا اسکریپت هستیم که با هدفگیری عنصر app# درون DOM تزریق میشود.
البته میتوانیم مقدار زیادی از آن را به یک فایل HTML اضافه کنیم. این موضوع به ترجیح شخصی شما بستگی دارد.
1document.getElementById("app").innerHTML = `
2<div class="base-timer">
3 <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
4 <g class="base-timer__circle">
5 <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45" />
6 </g>
7 </svg>
8 <span>
9 <!-- Remaining time label -->
10 </span>
11</div>
12`;
اکنون که نوعی markup داریم تا با آن کار کنیم، اقدام به استایلبندی آن میکنیم تا نمای بصری خوبی برای آغاز کار داشته باشیم. به طور خاص قصد انجام کارهای زیر را داریم:
- تعیین اندازه تایمر.
- حذف fill و stroke از عنصر پوششی دایره برای به دست آوردن یک شکل که زمان سپریشده از خلال آن نمایش مییابد.
- تعیین عرض و رنگ حلقه.
1/* Sets the containers height and width */
2.base-timer {
3 position: relative;
4 height: 300px;
5 width: 300px;
6}
7
8/* Removes SVG styling that would hide the time label */
9.base-timer__circle {
10 fill: none;
11 stroke: none;
12}
13
14/* The SVG path that displays the timer's progress */
15.base-timer__path-elapsed {
16 stroke-width: 7px;
17 stroke: grey;
18}
با انجام کارهای فوق، قالب مقدماتی ما شکلی مانند زیر پیدا میکند:
گام 2: تعیین برچسب زمان
همچنان که احتمالاً متوجه شدهاید، قالب ما شامل یک <SPAN> خالی است که باید زمان باقیمانده را داخل آن نشان دهیم. این مکان را با یک مقدار مناسب پر میکنیم. پیشتر گفتیم که زمان در قالب MM:SS نمایش مییابد. به این منظور یک متد به نام formatTimeLeft ایجاد میکنیم:
1function formatTimeLeft(time) {
2 // The largest round integer less than or equal to the result of time divided being by 60.
3 const minutes = Math.floor(time / 60);
4
5 // Seconds are the remainder of the time divided by 60 (modulus operator)
6 let seconds = time % 60;
7
8 // If the value of seconds is less than 10, then display seconds with a leading zero
9 if (seconds < 10) {
10 seconds = `0${seconds}`;
11 }
12 // The output in MM:SS format
13 return `${minutes}:${seconds}`;
14}
سپس از متد خود در قالب استفاده میکنیم:
1document.getElementById("app").innerHTML = `
2<div class="base-timer">
3 <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
4 <g class="base-timer__circle">
5 <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
6 </g>
7 </svg>
8 <span id="base-timer-label" class="base-timer__label">
9 ${formatTime(timeLeft)}
10 </span>
11</div>
12`
برای نمایش مقدار درون حلقه باید استایلهای خود را کمی بهروزرسانی کنیم:
1.base-timer__label {
2 position: absolute;
3
4 /* Size should match the parent container */
5 width: 300px;
6 height: 300px;
7
8 /* Keep the label aligned to the top */
9 top: 0;
10
11 /* Create a flexible box that centers content vertically and horizontally */
12 display: flex;
13 align-items: center;
14 justify-content: center;
15 /* Sort of an arbitrary number; adjust to your liking */
16 font-size: 48px;
17}
اکنون آماده هستیم تا با مقدار timeLeft کار کنیم، اما هنوز آن را ایجاد نکردهایم. ابتدا آن را ایجاد میکنیم و مقدار ابتدایی را روی محدودیت زمانی خود تنظیم میکنیم.
1// Start with an initial value of 20 seconds
2const TIME_LIMIT = 20;
3// Initially, no time has passed, but this will count up
4// and subtract from the TIME_LIMIT
5let timePassed = 0;
6let timeLeft = TIME_LIMIT;
اینک یک گام جلوتر هستیم:
اکنون تایمری داریم که در 20 ثانیه آغاز به کار میکند، اما هنوز عمل شمارش را انجام نمیدهد. در ادامه آن را کامل میکنیم تا ثانیه صفر را بشمارد.
گام 3: شمارش معکوس
اینک باید فکر کنیم برای شمارش معکوس زمان به چه چیز نیاز داریم. هم اینک یک مقدار timeLimit داریم که زمان ابتدایی ما را نشان میدهد و یک مقدار timePassed داریم که میزان زمان سپریشده از زمان آغاز شمارش معکوس را نشان میدهد. اینجا کاری که باید بکنیم این است که مقدار timePassed را یک واحد در هر ثانیه افزایش دهیم و مقدار timeLeft را بر اساس مقدار timePassed جدید محاسبه کند. این هدف با استفاده از setInterval قابل حصول است.
ابتدا متدی پیادهسازی میکنیم که startTimer را فراخوانی کند تا کارهای زیر را انجام دهد:
- تعیین بازه شمارنده.
- افزایش مقدار timePassed در هر ثانیه.
- محاسبه مجدد مقدار جدید timeLeft.
- بهروزرسانی مقدار برچسب در قالب.
همچنین باید ارجاعی به این شیء بازه نگهداری کنیم تا در زمان نیاز آن را پاک کنیم. به همین جهت است که متغیر timerInterval را ایجاد خواهیم کرد.
1let timerInterval = null;
2document.getElementById("app").innerHTML = `...`
3function startTimer() {
4 timerInterval = setInterval(() => {
5
6 // The amount of time passed increments by one
7 timePassed = timePassed += 1;
8 timeLeft = TIME_LIMIT - timePassed;
9
10 // The time left label is updated
11 document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
12 }, 1000);
13}
متدی داریم که با تایمر آغاز میشود، اما آن را هیچ کجا فراخوانی نمیکنیم. اینک تایمر را بیدرنگ پس از بارگذاری آغاز میکنیم.
1document.getElementById("app").innerHTML = `...`
2startTimer();
اینک تایمر ما شروع به شمارش معکوس زمان میکنیم. با این که این وضعیت مناسبی است، اما در صورتی که میتوانستیم به حلقه پیرامون برچسب زمانی خود رنگ بدهیم و در مقادیر زمانی مختلف رنگ آن را عوض کنیم، بسیار زیباتر میشد.
گام 4: پوشاندن تایمر با حلقه دیگر
برای بصری سازی زمان سپریشده، باید یک لایه دوم به حلقه خود اضافه کنیم که انیمیشن را مدیریت کند. کاری که انجام میدهیم اساساً پشتهسازی یک حلقه سبز رنگ جدید روی حلقه خاکستری اصلی است به طوری که حلقه سبز انیمیت شود و بدین ترتیب حلقه خاکستری را با گذشت زمان آشکار سازد و مانند یک نوار پیشروی عمل کند.
ابتدا یک عنصر path به عنصر SVG خود اضافه میکنیم.
1document.getElementById("app").innerHTML = `
2<div class="base-timer">
3 <svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
4 <g class="base-timer__circle">
5 <circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
6 <path
7 id="base-timer-path-remaining"
8 stroke-dasharray="283"
9 class="base-timer__path-remaining ${remainingPathColor}"
10 d="
11 M 50, 50
12 m -45, 0
13 a 45,45 0 1,0 90,0
14 a 45,45 0 1,0 -90,0
15 "
16 ></path>
17 </g>
18 </svg>
19 <span id="base-timer-label" class="base-timer__label">
20 ${formatTime(timeLeft)}
21 </span>
22</div>
23`;
سپس یک رنگ اولیه برای مسیر زمانی باقیمانده تعیین میکنیم:
1const COLOR_CODES = {
2 info: {
3 color: "green"
4 }
5};
6let remainingPathColor = COLOR_CODES.info.color;
در نهایت چند استایل اضافه میکنیم تا مسیر مدور مانند حلقه خاکستری اصلی به نظر بیاید. نکته مهم این است که مطمئن شویم stroke-width به همان اندازه حلقه اصلی است و این که مدت زمان transition روی ثانیهای تعیین شده است که به طرز همواری انیمیت میشود و متناظر با زمان باقیمانده روی برچسب زمانی است.
1.base-timer__path-remaining {
2 /* Just as thick as the original ring */
3 stroke-width: 7px;
4 /* Rounds the line endings to create a seamless circle */
5 stroke-linecap: round;
6 /* Makes sure the animation starts at the top of the circle */
7 transform: rotate(90deg);
8 transform-origin: center;
9 /* One second aligns with the speed of the countdown timer */
10 transition: 1s linear all;
11 /* Allows the ring to change color when the color value updates */
12 stroke: currentColor;
13}
14.base-timer__svg {
15 /* Flips the svg and makes the animation to move left-to-right
16 transform: scaleX(-1);
17}
بدین ترتیب یک استروک در خروجی ارائه میکنیم که حلقه تایمر را آن طور که باید، پوشش میدهد، اما هنوز انیمیت نمیشود تا حلقه تایمر را که گذشت زمان را نشان میدهد آشکار سازد.
برای انیمیت کردن خط طول زمان باقیمانده باید از مشخصه stroke-dasharray استفاده کنیم. روش کار در این آدرس (+) با تفصیل بیشتری توضیح داده است و مثالهایی از آن با استفاده از ترفندهای CSS توضیح داده شده است.
گام 5: انیمیت حلقه پیشروی
در این بخش به بررسی شیوه نمایش حلقه با مقادیر مختلف stroke-dasharray میپردازیم.
در تصویر فوق میبینیم که مقدار stroke-dasharray در عمل حلقه زمان باقیمانده را به بخشهای با طول برابر تقسیم میکند که طول آن برابر با مقدار زمان باقیمانده است. این اتفاق زمانی رخ میدهد که مقدار stroke-dasharray را روی یک عدد با رقم منفرد تنظیم میکنیم.
نام dasharray نشان میدهد که میتوانیم مقادیر چندگانه را به صورت یک آرایه تعیین کنیم. در ادامه به بررسی طرز رفتار آن در صورتی که دو عدد به جای یک عدد داشته باشیم میپردازیم. در این حالت آن مقادیر برابر با 10 و 30 هستند.
به این ترتیب طول بخش نخست (زمان باقیمانده) برابر با 10 و طول بخش دوم یعنی (زمان سپریشده) برابر با 30 تنظیم میشود. میتوانیم از آن در تایمر خود با کمی تغییر استفاده کنیم. چیزی که نیاز داریم ابتدا یک حلقه است که طول کامل دایره را بپوشاند، یعنی زمان باقیمانده برابر با طول حلقه ما باشد.
این طول چه قدر است؟ روش محاسبه این طول با استفاده از فرمول ریاضیاتی زیر است:
Length = 2πr = 2 * π * 45 = 282,6
این همان مقداری است که میخواهیم در زمان نمایش اولیه حلقه مورد استفاده قرار دهیم؛ طرز کار آن به صورت زیر است:
اینک مقدار اولیه در آرایه برابر با زمان باقیمانده ما است و مقدار دوم نیز میزان زمان سپریشده را نمایش میدهد. اینک باید مقدار نخست را دستکاری کنیم. در ادامه آنچه را که در زمان تغییر مقدار نخست میتوان انتظار داشت میبینیم:
دو متد ایجاد میکنیم که یکی مسئول محاسبه مقدار باقیمانده زمان و دیگری مسئول محاسبه مقدار stroke-dasharray و بهروزرسانی عنصر <path> است که زمان باقیمانده ما را نشان میدهد:
1// Divides time left by the defined time limit.
2function calculateTimeFraction() {
3 return timeLeft / TIME_LIMIT;
4}
5
6// Update the dasharray value as time passes, starting with 283
7function setCircleDasharray() {
8 const circleDasharray = `${(
9 calculateTimeFraction() * FULL_DASH_ARRAY
10 ).toFixed(0)} 283`;
11 document
12 .getElementById("base-timer-path-remaining")
13 .setAttribute("stroke-dasharray", circleDasharray);
14}
همچنین باید مسیر خود را در هر ثانیه که سپری میشود بهروزرسانی کنیم. این بدان معنی است که باید متد جدیداً ایجاد شده setCircleDasharray را درون timerInterval فراخوانی کنیم.
1function startTimer() {
2 timerInterval = setInterval(() => {
3 timePassed = timePassed += 1;
4 timeLeft = TIME_LIMIT - timePassed;
5 document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
6
7 setCircleDasharray();
8 }, 1000);
9}
اکنون میبینیم که در حال حرکت است:
کد ما اکنون کار میکند، اما اگر به دقت به خصوص به آخر نگاه کنید به نظر میرسد که انیمیشن ما یک ثانیه تأخیر دارد. زمانی که به ثانیه 0 میرسد، بخش کوچکی از حلقه همچنان نمایان است.
دلیل این امر ناشی از این نکته است که مدت زمان انیمیت روی یک ثانیه تنظیم شده است. در وضعیتی که مقدار باقیمانده زمان روی صفر تنظیم شود، همچنان به یک ثانیه زمان نیاز دارد تا در عمل انیمیشن حلقه را به پایان ببرد. با کاهش طول حلقه به تدریج در طی شمارش معکوس میتوانیم این مشکل را نیز رفع کنیم. این کار را در متد calculateTimeFraction انجام میدهیم:
1function calculateTimeFraction() {
2 const rawTimeFraction = timeLeft / TIME_LIMIT;
3 return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
4}
اینک نتیجه کار به صورت زیر است:
یک نکته دیگر نیز وجود دارد که باید مورد اشاره قرار دهیم. پیشتر گفتیم که خواستار تغییر رنگ نشانگر پیشروی در حالتی هستیم که زمان باقیمانده به نقطه مشخصی برسد. این نوع کارها موجب میشود که کاربر بداند که زمانش تقریباً رو به اتمام است.
گام 6: تغییر رنگ نوار پیشروی در زمان خاص
ابتدا باید دو آستانه اضافه کنیم که نشان میدهد رنگ در چه زمانی باید تغییر یابد و یکی نشانگر حالت هشدار و دیگری حالت اخطار است و برای هر دو حالت نیز رنگهایی تعیین میکنیم. کار خود را با رنگ سبز آغاز میکنیم و سپس رنگ را به نارنجی تغییر میدهیم تا نشانگر حالت هشدار باشد. در ادامه از رنگ قرمز برای نمایش حالت اخطار در حالتی که زمان تقریباً رو به اتمام است استفاده میکنیم.
1// Warning occurs at 10s
2const WARNING_THRESHOLD = 10;
3// Alert occurs at 5s
4const ALERT_THRESHOLD = 5;
5const COLOR_CODES = {
6 info: {
7 color: "green"
8 },
9 warning: {
10 color: "orange",
11 threshold: WARNING_THRESHOLD
12 },
13 alert: {
14 color: "red",
15 threshold: ALERT_THRESHOLD
16 }
17};
اکنون متدی ایجاد میکنیم که مسئول بررسی این نکته است که آیا از آستانه مورد نظر عبور کردهایم یا نه و رنگ نوار پیشروی را در زمانی که این اتفاق بیفتد تغییر میدهد.
1function setRemainingPathColor(timeLeft) {
2 const { alert, warning, info } = COLOR_CODES;
3 // If the remaining time is less than or equal to 5, remove the "warning" class and apply the "alert" class.
4 if (timeLeft <= alert.threshold) {
5 document
6 .getElementById("base-timer-path-remaining")
7 .classList.remove(warning.color);
8 document
9 .getElementById("base-timer-path-remaining")
10 .classList.add(alert.color);
11 // If the remaining time is less than or equal to 10, remove the base color and apply the "warning" class.
12 } else if (timeLeft <= warning.threshold) {
13 document
14 .getElementById("base-timer-path-remaining")
15 .classList.remove(info.color);
16 document
17 .getElementById("base-timer-path-remaining")
18 .classList.add(warning.color);
19 }
20}
بنابراین ابتدا کلاس CSS را در زمانی که تایمر به نقطه خاصی برسد حذف میکنیم و کلاس دیگری به جای آن اضافه میکنیم. قصد داریم این کلاسها را تعریف کنیم.
1.base-timer__path-remaining.green {
2 color: rgb(65, 184, 131);
3}
4.base-timer__path-remaining.orange {
5 color: orange;
6}
7.base-timer__path-remaining.red {
8 color: red;
9}
اکنون چیزی را که میخواستیم به دست آوردیم. دموی نهایی این پروژه به صورت زیر است:
کدهای نهایی این پروژه را از این لینک (+) دانلود کنید. اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای طراحی سایت با HTML و CSS
- آموزش جاوا اسکریپت (JavaScript)
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- جاوا اسکریپت چیست؟ — به زبان ساده
- ساخت تایمر شمارش معکوس با SwiftUI — از صفر تا صد
==
سلام
بسیار کاربردی و خوب .
ممنون از نوسنده و سایت خوب شما.
سلام وقتتون بخیر من هرچقدر سعی کردم فایل css و java script رو با html مخلوط کنم موفق نشدم میشه راهنماییم کنید؟
با سلام و احترام؛
صمیمانه از همراهی شما با مجله فرادرس و ارائه بازخورد سپاسگزاریم.
پیشنهاد میشود پیش از اجرای کدهای این مقاله، ابتدا مقاله زیر را مطالعه کنید تا با نحوه اجرا و پیادهسازی کدها و پروژههای جاوا اسکریپت بیشتر آشنا شوید:
همچنین میتوانید از دورههای آموزشی زیر برای یادگیری بیشتر استفاده کنید:
برای شما آرزوی سلامتی و موفقیت داریم.
با سلام!
من این کد روی در کروم اجرا کردم ولی درست اجرا نمیشه و فقط صفحه صفید میاد!
باسلام!
شما باید کد سی اس اس و جاوا اسکیریپ را با html مخلوط کنید.
سلام خسته نباشید استاد ,من میخواستم یک تایمر شمارش معکوس داخل اسلایدر یا حلقه داشته باشم اما کد جاوااسکریپتی که شما اموزش دادید یا از سایت های دیگه که برمیدارم و جایگزاری میکنم فقط در یکی از اسلایدرها یا حلقه ها می ایند میشه کدی بدید که در همه مطالب اسلایدرها یاهمون حلقه هابیایدمن خیلی به کمک شما احتیاج دارم لطفا کمکم کنید
سلام لطفا کد هایه کامل ثانیه شماررو هم برای دانلود داخل سایت قرار بدید
با سلام.
کدهای کامل پروژه به صورت یک فایل فشرده در لینک انتهای متن نیز ارائه شد.
با تشکر از توجه شما.
با سلام
کدهای این ا
سلام
با تشکر از آموزش خوبتون .
کد های نهایی رو هم میزارید؟