Daktilo efekti, birinin sesli düşünmesini izlemenin dijital karşılığıdır. Metin, sanki gerçek bir el gerçek tuşlara basıyormuş gibi, her seferinde bir karakter olacak şekilde görünür. Bunu portfolyo sitelerinin hero bölümlerinde, tarayıcı tabanlı terminal emülatörlerinde ve bir metin bloğunu önceden hazırlanmış bir şekilde getirmek yerine "yazıyormuş" gibi görünmek isteyen yapay zeka asistanlarının sohbet pencerelerinde görürsünüz. İyi yapıldığında bir beklenti yaratır. Kötü yapıldığında ise 1987'de sıkışmış bir yazıcı gibi hissettirir.
Bu Desen Neden Kalıcılığını Koruyor
Bilgisayarlar bilgiyi anında iletir. İnsanlar iletmez. Bu iki hız arasındaki boşluk kullanışlıdır. Daktilo efekti, insan hızını simüle ederek bu boşluğu doldurur. Bir açılış sayfasında, ziyaretçilerin değer önerisini sadece göz gezdirip geçmek yerine gerçekten okumalarını sağlamak için bir başlığı kelime kelime takip etmeye yönlendirebilir. Bir terminal emülatöründe, komutların gerçek zamanlı olarak yürütüldüğü illüzyonunu yaratır. Bir sohbet botu arayüzünde ise ritim, bir yanıtın veri tabanından getirilmek yerine o anda oluşturulduğu sinyalini verir.
Ancak bu efekt, mekanikler kullanıcıya saygı duyduğu sürece işe yarar. Aynı aralıklarla gelen düz, metronomik bir tık-tık-tık sesi robotik hissettirir. Daha da kötüsü, yardımcı teknolojileri göz ardı eden bir uygulama, eğlenceli bir görsel süslemeyi sinir bozucu bir engele dönüştürebilir. Amaç kullanıcıyı yavaşlatmak değil; arayüzün canlı hissettirmesi için sadece yeterli miktarda sürtünme eklemektir.
Recursive setTimeout ile İnşa Edin
setTimeout ile başlayın ve setInterval'e dokunmayın. Aradaki fark göründüğünden daha önemlidir.
setInterval inatçıdır. Scriptinizde veya tarayıcının ana iş parçacığında (main thread) başka neler olup bittiğine bakmaksızın her n milisaniyede bir çalışır. Eğer mantığınız normal tuş vuruşları arasında 50 milisaniyelik, noktalama işaretlerinden sonra ise 150 milisaniyelik bir duraklamaya ihtiyaç duyuyorsa, setInterval buna uyum sağlayamaz. Sonuçta onu ekstra koşullu mantıklarla sarmalamak, yarış durumlarıyla (race conditions) savaşmak ve sonunda aralığı o kadar sık temizleyip sıfırlamak zorunda kalırsınız ki kod bir durum yönetimi kabusuna dönüşür. Sabit aralıklar, değişken hızlara ihtiyaç duyduğunuz anda başarısız olur.
Recursive setTimeout, her adımın bir sonraki adımın kurallarını belirlemesine izin vererek bu sorunu çözer. Bunu küçük bir durum makinesi (state machine) gibi düşünün. Birkaç değişken tutarsınız: mevcut metin, mevcut karakter indeksi, yazıyor mu yoksa siliyor mu olduğunuzu belirten bir boolean bayrak ve birden fazla metin arasında döngü kurabilmeniz için bir textIndex sayacı. Fonksiyon bir karakter ekler, cümle içinde nerede olduğunu kontrol eder ve ardından bağlama uygun bir gecikmeyle kendi bir sonraki çağrısını planlar.
Örneğin, çoğu karakteri 50 milisaniyede yazabilir, bir virgülden sonra 150 milisaniyeye yavaşlayabilir ve silme moduna geçmeden önce tam bir cümlenin sonunda 800 milisaniye duraklayabilirsiniz. Bunu setInterval ile temiz bir şekilde yapamazsınız. Recursive setTimeout ile mantık basittir:
if typing:
append next character
if at end of string:
switch to pause mode
schedule next call after 1000ms
if deleting:
remove last character
if string empty:
increment textIndex
load next string
switch to typing mode
Bu yapı aynı zamanda temizliği de çok kolaylaştırır. Timeout ID'sini saklayın. Bileşen unmount olduğunda veya kullanıcı başka bir sayfaya geçtiğinde, clearTimeout fonksiyonunu bir kez çağırın. Arka planda çalışan sahipsiz aralıklar kalmaz.
İmleç Kendi Başına Yanıp Sönmeli
Yanıp sönen imleç bir veri meselesi değil, görsel bir detaydır. Onu JavaScript durum motorunuzun dışında tutun. Bir ::after pseudo-elementine bağlı ayrı bir CSS animasyonu veya metin kapsayıcınızın sonunda duran özel bir <span kullanın.
step-end zamanlamasıyla opacity veya border-color değerlerini değiştiren basit bir @keyframes blink, compositor üzerinde çalışan keskin ve donanım dostu bir nabız etkisi sağlar. İmleç görünürlüğünü mikro yönetmek JavaScript'in işi değildir. Eğer setTimeout özyinelemesinin (recursion) içinden görüntüleme özelliklerini değiştirirseniz, her bir karakterde gereksiz stil yeniden hesaplamalarına (style recalculations) zorlamış olursunuz. Estetiği CSS'e bırakın. Sıralamayı JavaScript yönetsin.
Birden fazla metin arasında döngü kurmak, bir textIndex sayacı ile oldukça basittir. Metinlerinizi bir dizide saklayın. Animasyon silme aşamasını bitirdiğinde ve kapsayıcı boşaldığında, textIndex değerini dizinin uzunluğuna göre modulo işlemiyle artırın, karakter işaretçisini sıfıra sıfırlayın ve tekrar yazmaya başlayın. Portfolyo siteleri, sayfayı hiç yenilemeden ["Developer", "Designer", "Writer"] gibi rolleri bu şekilde döndürür.
İllüzyonu Bozan Hatalar
Amatör uygulamalarda üç hata tekrar tekrar karşımıza çıkar.
Using setInterval. We have already covered the variable-speed problem, but there is a subtler issue. If your DOM update ever lags—say, because the browser is painting a layout shift—setInterval keeps firing. You can end up with overlapping writes, duplicate characters, or writes that happen faster than the browser can render them. Recursive setTimeout waits until the current step is done before it even thinks about the next one.
Forgetting to escape HTML. If your source strings contain angle brackets, and you are injecting content via innerHTML one character at a time, you will split tags in half. The browser sees <, then <s, then <st. That prevents proper tag parsing and can leave you with broken DOM nodes or unexpected styling cascades. If you want the literal characters to appear, escape them first or, better yet, write to textContent instead of innerHTML. If you genuinely need styled spans inside the typewriter output, pre-process the string so you know exactly where tags begin and end before you start the character loop.
Ignoring accessibility. Screen readers do not enjoy being read to one letter at a time. As your script appends each new character to the DOM, some assistive technologies announce the entire node again, causing a staccato barrage of partial words. That is a nightmare for anyone relying on auditory navigation. The fix is not complicated: add an aria-label to the container that holds the full, final text. You can also hide the animated element from assistive tech entirely with aria-hidden="true" and provide a visually hidden static copy for screen readers. Either way, give users the full sentence up front instead of forcing them to sit through your performance.
Ways to Polish Your Version
Once the core loop behaves, you can layer on extras. But resist the urge to add them until the fundamentals are solid.
Keystroke sound effects. A subtle click on each character can be satisfying, but audio on webpages is a minefield. Use the Web Audio API or a lightweight Audio element with a short buffer. Vary the playback rate slightly—between 0.95 and 1.05—so identical clicks do not sound synthetic. Always respect the browser’s autoplay policies and provide a mute toggle. Nothing drives users away faster than an unmuted portfolio page AutoPlaying typing sounds at 9 AM.
Multi-line typing. Real terminal windows wrap. If your text crosses a line break, a simple border-right cursor will jump awkwardly unless your layout is predictable. Split strings by newline characters and render each line in its own <span>, or use a positioned pseudo-element that tracks the end of the content. Be careful with text wrapping; a cursor implemented as an inline border can detach from the text if the container width shifts. Consider using white-space: pre-wrap and a monospaced font for terminal styles, since fixed-width characters make cursor math far more predictable.
Real-time Markdown rendering. This is where things get tricky. If you type **bold**, you have a choice: render the asterisks literally as they appear, or convert them to a bold style on the fly. If you choose the latter, switching from textContent to innerHTML mid-stream means your text node boundaries change. The cursor position becomes a bookkeeping headache because HTML tags shift the DOM tree underneath you. One safer approach is to type the raw Markdown string normally, then trigger a render pass once the full string is on screen. If you truly need live formatting, maintain two layers: a hidden typed buffer and a parsed visual overlay.
Reverse effects. Deleting text does not have to mean backspacing one character at a time. You can simulate a “select all, then delete” reset that clears the field instantly before the next string types in. That feels clinical. Alternatively, slow backspacing at 30 milliseconds per character builds tension. Mix the two: backspace through a typo quickly, pause, then resume deleting at normal speed. The variation sells the humanity of the effect.
The Real Takeaway
A typewriter effect is one of those UI flourishes that looks trivial on the surface and reveals its complexity only after you have built it. Start with
