
Pratik “yaparak öğrenme” için üç küçük ama işe yarar vanilla JavaScript projesi kuracağız:
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.
Ş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.
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.
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>
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();
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.
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>
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();
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.
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>
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();
});
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.
Yorumlar