Javascript 30 - Gün 2

18 Mart 2023

Challenge'ın ikinci gününde JS ve CSS kullanarak analog bir saat hazırlamamız gerekiyor. Projenin demosuna buradan erişebilirsiniz.

Başlangıç dosyalarını bulabileceğiniz repom: js-30 | github

1. CSS ile çubukları hareket ettirmek

Her akrep, yelkovan ve saniye çubuklarını doğru pozisyona getirmek için transform:rotate() özelliği kullanılabilir. Ancak bu özelliği kullandığımızda varsayılan olarak çubuk tam ortasından döndürülür, bunun yerine en sonundan döndürülmesi için transform-origin değerini 100% yapmalıyız.

Dolayısıyla akrep, yelkovan ve saniye çubuğu tarafından kullanılan .hand sınıfını şu şekilde güncelleyelim:

.hand {
  width: 50%;
  height: 6px;
  background: black;
  position: absolute;
  top: 50%;
  transform-origin: 100%;
}

2. JS ile her saniye çubukları doğru pozisyona getirmek

JS tarafında her çubuğu seçip, her saniye doğru açıda olmalarını sağlamamız gerekiyor :

<script>
const secondHand = document.querySelector(".second-hand");
const minuteHand = document.querySelector(".min-hand");
const hourHand = document.querySelector(".hour-hand");
 
function updateClock() {
	const now = new Date();
 
	const seconds = now.getSeconds();
	// saniyenin açı değerini saklıyoruz böylece bu değeri dakikaya ekleyerek dakika çubuğunun saniye ile senkronize bir şekilde oynamasını sağlıyoruz
	// örneğin dakika 30 ve saniye 20 ise dakikaya 20/60 = 0.33 dakika daha ekleyerek doğru açıda olmasını sağlayacağız.
	const secondRatio = seconds / 60;
	// saniyenin açısını hesaplarken +90 eklememizin sebebi varsayılan olarak çubukların saat 9 yönünde başlamalırıdır.
	// +90 ekleyerek saatin 12'den başlamasını sağlıyoruz
	const secondDegree = secondRatio * 360 + 90;
 
	secondHand.style.transform = `rotate(${secondDegree}deg)`;
 
	const minutes = now.getMinutes();
	// aynı şekilde dakikanın açı değerini de saklayarak bunu saate ekleyeceğiz böylece saat çubuğu da dakika çubuğuyla senkronize olacak
	const minuteRatio = (minutes + secondRatio) / 60;
	const minuteDegree = minuteRatio * 360 + 90;
 
	minuteHand.style.transform = `rotate(${minuteDegree}deg)`;
 
	const hours = now.getHours();
	const hourRatio = (hours + minuteRatio) / 12;
	const hourDegree = hourRatio * 360 + 90;
 
	hourHand.style.transform = `rotate(${hourDegree}deg)`;
}
 
// saatin her saniye güncellenmesi için setInterval ile updateClock fonksiyonumuzu her saniye çalıştıralım
setInterval(updateClock, 1000);
</script>

Saatin doğru bir şekilde hareket ettiğini görebilirsiniz:

3.cubic-bezier ile daha gerçekçi bir animasyon

Çubukların açısı değiştiğinde ani bir şekilde güncelleniyor. Bu geçişi gerçekci yapmak için internetten bulduğum cubic-bezier'i ekliyorum.

.hand {
  width: 50%;
  height: 6px;
  background: black;
  position: absolute;
  top: 50%;
  transform-origin: 100%;
  transition: transform 0.3s cubic-bezier(0.4, 2.08, 0.55, 0.44);
}

Yukarıdaki gibi gerçekçi bir geçiş sağlanıyor. ANCAK saniye 0 olduğunda animasyonun bozulduğunu görüyoruz. Bunun sebebi:

  1. Saniye 59 iken açımız (59/ 60) * 360 + 90 = 444
  2. Saniye 0 olduğu anda açımız (0/60) * 360 + 90 = 90 oluyor ve eklediğimiz transition yüzünden 0.3 saniye içinde çubuğun 444deg -> 90deg arasındaki geçişini görüyoruz.

Bunu engellemek için JS tarafında şunları eklememiz yeterli olacaktır:

function updateClock() {
  const now = new Date();
 
  const seconds = now.getSeconds();
  // saniyenin açı değerini saklıyoruz böylece bu değeri dakikaya ekleyerek dakika çubuğunun saniye ile senkronize bir şekilde oynamasını sağlıyoruz
  // örneğin dakika 30 ve saniye 20 ise dakikaya 20/60 = 0.33 dakika daha ekleyerek doğru açıda olmasını sağlayacağız.
  const secondRatio = seconds / 60;
  // saniyenin açısını hesaplarken +90 eklememizin sebebi varsayılan olarak çubukların saat 9 yönünde başlamalırıdır.
  // +90 ekleyerek saatin 12'den başlamasını sağlıyoruz
  const secondDegree = secondRatio * 360 + 90;
 
  // saniyenin 0 olduğunda transition kaynaklı bug için
  // saniye 0 ise transition süresini 0 yapalım aksi sonrasında tekrar eski haline çevirelim
  if (seconds === 0) {
    secondHand.style.transitionDuration = "0s";
    secondHand.style.transform = `rotate(${secondDegree}deg)`;
    setTimeout(() => {
      secondHand.style.transitionDuration = "0.3s";
    }, 0);
  } else {
    secondHand.style.transform = `rotate(${secondDegree}deg)`;
  }
 
  const minutes = now.getMinutes();
  // aynı şekilde dakikanın açı değerini de saklayarak bunu saate ekleyeceğiz böylece saat çubuğu da dakika çubuğuyla senkronize olacak
  const minuteRatio = (minutes + secondRatio) / 60;
  const minuteDegree = minuteRatio * 360 + 90;
 
  minuteHand.style.transform = `rotate(${minuteDegree}deg)`;
 
  const hours = now.getHours();
  const hourRatio = (hours + minuteRatio) / 12;
  const hourDegree = hourRatio * 360 + 90;
 
  hourHand.style.transform = `rotate(${hourDegree}deg)`;
}

Bu projeden öğrendiklerim

  • transform-origin ile CSS'de gerçekleştirilen transformların nereden başlanacağı belirlenebilir ve varsayılan olarak 50%'dir.
GitHubBu sayfayı düzenle