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 <dialog> 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.