Bu yazıda ne yapacağız?

Pratik “yaparak öğrenme” için üç küçük ama işe yarar vanilla JavaScript projesi kuracağız:

  • Todo uygulaması: Ekle / tamamlandı işaretle / sil + localStorage ile kalıcılık
  • Sayaç: Başlat / durdur / sıfırla + setInterval / clearInterval
  • Modal: Modern ve semantik <dialog> ile aç/kapat

Hedef, “çok kapsamlı framework projesi” değil; DOM seçme, event yönetimi, durum (state) tutma ve küçük UI akışları gibi temel kasları güçlendirmek.


Ön koşullar ve kurulum

Şunlar yeterli: temel HTML, çok az CSS ve temel JavaScript. İsterseniz her projeyi ayrı dosyada, isterseniz tek dosyada deneyebilirsiniz. Aşağıdaki şablonla başlayın.

index.html (başlangıç şablonu)
<!doctype html>
<html lang="tr">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JS Mini Projeler</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial; margin: 24px; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; margin: 16px 0; }
ul { padding-left: 18px; }
li.done { text-decoration: line-through; opacity: 0.7; }
button { cursor: pointer; }
</style>
<h1>3 Hızlı JavaScript Mini Projesi</h1>
<!-- Aşağıdaki bölümlere projelerin HTML'lerini ekleyeceğiz -->
<script>
// Aşağıdaki bölümlere projelerin JS kodlarını ekleyeceğiz
</script>
</html>

Not: Bu rehber, olay bağlamak için addEventListener yaklaşımını kullanır. MDN, event dinleme için bu yöntemin standart ve sürdürülebilir olduğunu detaylıca açıklar. Kaynak: MDN addEventListener.


Mini Proje 1: localStorage’lı Todo Uygulaması

Ne öğreneceksiniz?

  • Input’tan veri alma ve listeye basma
  • Tek tek öğelere tıklama/işaretleme
  • Silme işlemi
  • localStorage ile sayfa kapanıp açılsa bile veriyi koruma

MDN’e göre Web Storage API, bir origin için anahtar-değer mantığında depolama sağlar ve tarayıcı oturumları arasında veriyi tutabilir. Kaynak: MDN Web Storage API.

Todo HTML

HTML (Todo kartı)
<div class="card" id="todoCard">
<h2>Todo</h2>
<div class="row">
<input id="todoInput" placeholder="Yeni iş..." />
<button id="addTodoBtn">Ekle</button>
<button id="clearTodosBtn" title="Tümünü sil">Temizle</button>
</div>
<p id="todoHint"><em>İpucu: Öğeye tıklayınca tamamlandı olur.</em></p>
<ul id="todoList"></ul>
</div>

Todo JavaScript (kopyala-yapıştır)

Kritik nokta: localStorage yalnızca string saklar. Dizi/nesne saklamak için JSON.stringify, geri okurken JSON.parse kullanın. Kaynak: MDN Web Storage API.

JS (Todo)
const todoInput = document.querySelector('#todoInput');
const addTodoBtn = document.querySelector('#addTodoBtn');
const clearTodosBtn = document.querySelector('#clearTodosBtn');
const todoListEl = document.querySelector('#todoList');

const STORAGE_KEY = 'miniProjects.todos.v1';
let todos = [];

function storageAvailable(type) {
try {
const storage = window[type];
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return false;
}
}

function loadTodos() {
if (!storageAvailable('localStorage')) {
// Depolama kullanılamazsa uygulama yine çalışsın; sadece kalıcılık olmaz.
return;
}
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return;
try {
todos = JSON.parse(raw) ?? [];
} catch (e) {
todos = [];
}
}

function saveTodos() {
if (!storageAvailable('localStorage')) return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
}

function renderTodos() {
todoListEl.innerHTML = '';

todos.forEach((t) => {
const li = document.createElement('li');
li.textContent = t.text;
li.dataset.id = t.id;
if (t.done) li.classList.add('done');

const delBtn = document.createElement('button');
delBtn.textContent = 'Sil';
delBtn.style.marginLeft = '8px';
delBtn.addEventListener('click', (ev) => {
ev.stopPropagation();
deleteTodo(t.id);
});

li.appendChild(delBtn);
li.addEventListener('click', () => toggleDone(t.id));
todoListEl.appendChild(li);
});
}

function addTodo(text) {
const trimmed = text.trim();
if (!trimmed) return;
todos.unshift({ id: crypto.randomUUID ? crypto.randomUUID() : String(Date.now()), text: trimmed, done: false });
saveTodos();
renderTodos();
}

function toggleDone(id) {
todos = todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t));
saveTodos();
renderTodos();
}

function deleteTodo(id) {
todos = todos.filter((t) => t.id !== id);
saveTodos();
renderTodos();
}

addTodoBtn.addEventListener('click', () => {
addTodo(todoInput.value);
todoInput.value = '';
todoInput.focus();
});

todoInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
addTodoBtn.click();
}
});

clearTodosBtn.addEventListener('click', () => {
todos = [];
saveTodos();
renderTodos();
});

loadTodos();
renderTodos();

Todo için pratik notlar

  • Depolama her zaman çalışmayabilir: Özel mod, tarayıcı ayarları veya kota sınırları gibi nedenlerle localStorage kullanımı kısıtlanabilir. Bu yüzden yukarıdaki gibi “kullanılabilir mi?” kontrolü yapmak faydalıdır. (MDN örnek yaklaşımı: Web Storage API)
  • Hassas veri kaydetmeyin: localStorage, sayfadaki JavaScript tarafından okunabilir. Bu nedenle parola/token gibi hassas bilgiler için uygun bir yer değildir. Todo gibi düşük riskli içerikler için daha makul bir tercihtir.
  • Event yaklaşımı: Inline onclick yerine addEventListener ile olay bağlamak, kodu daha bakımı kolay hale getirir. (Kaynak: MDN addEventListener)

Mini Proje 2: setInterval ile Sayaç (Başlat/Durdur/Sıfırla)

Ne öğreneceksiniz?

  • Zamanlayıcı başlatma ve durdurma
  • Interval ID saklama
  • UI güncelleme

MDN’e göre setInterval, bir fonksiyonu belirli aralıklarla çalıştırır; durdurmak için clearInterval gerekir. Ayrıca zamanlama, tarayıcının olay döngüsü nedeniyle “mükemmel dakiklikte” olmayabilir. Kaynak: MDN setInterval.

Sayaç HTML

HTML (Sayaç kartı)
<div class="card" id="counterCard">
<h2>Sayaç</h2>
<p>Geçen süre: <strong id="counterValue">0</strong> sn</p>
<div class="row">
<button id="startCounterBtn">Başlat</button>
<button id="pauseCounterBtn">Durdur</button>
<button id="resetCounterBtn">Sıfırla</button>
</div>
</div>

Sayaç JavaScript

JS (Sayaç)
const counterValueEl = document.querySelector('#counterValue');
const startCounterBtn = document.querySelector('#startCounterBtn');
const pauseCounterBtn = document.querySelector('#pauseCounterBtn');
const resetCounterBtn = document.querySelector('#resetCounterBtn');

let seconds = 0;
let intervalId = null;

function renderCounter() {
counterValueEl.textContent = String(seconds);
}

function startCounter() {
if (intervalId !== null) return; // zaten çalışıyor
intervalId = window.setInterval(() => {
seconds += 1;
renderCounter();
}, 1000);
}

function pauseCounter() {
if (intervalId === null) return;
window.clearInterval(intervalId);
intervalId = null;
}

function resetCounter() {
pauseCounter();
seconds = 0;
renderCounter();
}

startCounterBtn.addEventListener('click', startCounter);
pauseCounterBtn.addEventListener('click', pauseCounter);
resetCounterBtn.addEventListener('click', resetCounter);

renderCounter();

Sayaç için pratik notlar

  • Zaman kayması normal olabilir: setInterval, ideal senaryoda her 1000 ms’de bir çalışır; ancak sekme arka plana gidince veya ana iş parçacığı yoğunken gecikmeler görülebilir. (Kaynak: MDN setInterval)
  • Daha “ölçüme yakın” sayaç isterseniz: seconds’ı arttırmak yerine başlangıç zamanını saklayıp Date.now() farkından hesaplama yapabilirsiniz. Bu yaklaşım, gecikmeleri daha iyi tolere eder.

Mini Proje 3: <dialog> ile Modern Modal

Neden <dialog>?

Modal pencereler, klasik olarak div + overlay ile yapılır. Modern tarayıcılarda <dialog> elementi, semantik bir seçenek sunar ve showModal() / close() gibi API’lerle gelir. MDN ayrıca showModal ile açıldığında dış alanın etkileşimini engelleme (inert davranışı) ve ESC ile kapatma gibi davranışları açıklar. Kaynak: MDN dialog.

Modal HTML

HTML (Modal kartı + dialog)
<div class="card" id="modalCard">
<h2>Modal</h2>
<button id="openModalBtn">Modal Aç</button>
</div>

<dialog id="demoDialog">
<h3>Merhaba!</h3>
<p>Bu bir &lt;dialog&gt; modal örneğidir.</p>
<div class="row">
<button id="closeModalBtn">Kapat</button>
</div>
</dialog>

Modal JavaScript

JS (Modal)
const openModalBtn = document.querySelector('#openModalBtn');
const dialogEl = document.querySelector('#demoDialog');
const closeModalBtn = document.querySelector('#closeModalBtn');

function canUseDialog() {
return typeof HTMLDialogElement !== 'undefined' && typeof dialogEl.showModal === 'function';
}

openModalBtn.addEventListener('click', () => {
if (!canUseDialog()) {
// Basit bir geri dönüş: En azından mesajı göster
window.alert('Tarayıcınız dialog özelliğini desteklemiyor olabilir.');
return;
}
dialogEl.showModal();
});

closeModalBtn.addEventListener('click', () => dialogEl.close());

// İsteğe bağlı: dialog dışına tıklayınca kapat (kullanıcı deneyimi tercihi).
dialogEl.addEventListener('click', (e) => {
const rect = dialogEl.getBoundingClientRect();
const clickedInDialog = (
e.clientX >= rect.left && e.clientX <= rect.right &&
e.clientY >= rect.top && e.clientY <= rect.bottom
);
if (!clickedInDialog) dialogEl.close();
});

Modal için erişilebilirlik ve uyumluluk notları

  • ESC ile kapatma: <dialog> modal modda açıldığında çoğu modern tarayıcı ESC davranışını destekler. Detaylar: MDN dialog.
  • Odak (focus) yönetimi: <dialog> bu konuda yardımcı olur; yine de modal açılınca ilk etkileşimli öğeye odak vermek iyi bir alışkanlıktır (ör. kapat butonu).
  • Eski tarayıcılar: <dialog> her ortamda aynı şekilde çalışmayabilir. Üretim senaryosunda bir polyfill veya role="dialog" tabanlı erişilebilir bir geri dönüş yaklaşımı değerlendirin.

Hepsini toparlayan mini kontrol listesi

  • State: Veriyi (todos, seconds gibi) tek bir değişkende tutup UI’ı o state’ten üretin.
  • Render fonksiyonu: DOM güncellemelerini tek yerde yapın (renderTodos, renderCounter).
  • Event yönetimi: Inline handler yerine addEventListener kullanın. (Kaynak: MDN addEventListener)
  • Kalıcılık: localStorage ile saklarken JSON.stringify/parse uygulayın. (Kaynak: MDN Web Storage API)
  • Zamanlayıcılar: setInterval başlatmayı/bitirmeyi kontrol edin; sekme arka planda farklı davranışlar olabileceğini unutmayın. (Kaynak: MDN setInterval)

Bir sonraki adım: Mini projeleri büyütme fikirleri

  • Todo: Filtre (tümü/aktif/tamamlanan), düzenleme (edit), sürükle-bırak sıralama.
  • Sayaç: Pomodoro (25/5), tur (lap) kayıtları, sesli bildirim.
  • Modal: Onay penceresi (confirm), form içeren modal, klavye kısayolları.

Kodları hızlıca denemek için bir online editöre (ör. https://codepen.io/) tek HTML olarak yapıştırıp çalıştırabilirsiniz. Gerçek bir proje ortamında ise dosyaları ayırmanız (app.js, styles.css) okunabilirliği artırır.

Genel not: Bu yazı eğitim amaçlıdır. Üretim uygulamalarında tarayıcı uyumluluğu, erişilebilirlik testleri ve veri güvenliği gereksinimleri ayrıca değerlendirilmelidir.