टाईपरायटर इफेक्ट म्हणजे कोणीतरी मोठ्याने विचार करत आहे हे पाहण्यासारखे डिजिटल समकक्ष आहे. मजकूर एका वेळी एक कॅरेक्टर याप्रमाणे दिसतो, जणू काही एखादा खरा हात खरोखरच्या कीज (keys) दाबतोय. तुम्ही हे पोर्टफोलिओ साइट्सच्या हिरो सेक्शन्समध्ये, ब्राउझर-आधारित टर्मिनल इम्युलेटर्समध्ये आणि AI असिस्टंट्सच्या चॅट विंडोजमध्ये पाहू शकता, जे हे सिद्ध करू इच्छितात की ते केवळ तयार मजकूर दाखवत नसून प्रत्यक्षात "टाईप" करत आहेत. जेव्हा हे व्यवस्थित केले जाते, तेव्हा ते उत्सुकता निर्माण करते. पण जर ते चुकीच्या पद्धतीने केले, तर ते १९८७ मध्ये अडकलेल्या प्रिंटरसारखे वाटते.

हा पॅटर्न का टिकून आहे

संगणक माहिती त्वरित पोहोचवतात. मानव तसे करत नाही. या दोन गतीमधील अंतर उपयुक्त आहे. टाईपरायटर इफेक्ट मानवी गतीचे अनुकरण करून हे अंतर भरून काढतो. लँडिंग पेजवर, हे हेडलाईनमधील शब्द एकामागून एक दाखवून प्रेक्षकांचे लक्ष वेधून घेऊ शकते, जेणेकरून व्हिजिटर्स केवळ वरवर वाचण्याऐवजी (skimming) व्हॅल्यू प्रपोझिशन खरोखर वाचतील. टर्मिनल इम्युलेटरमध्ये, हे कमांड्स रिअल टाइममध्ये कार्यान्वित होत आहेत असा आभास निर्माण करते. चॅटबॉट इंटरफेसमध्ये, ही लय सूचित करते की प्रतिसाद डेटाबेस मधून काढण्याऐवजी तात्काळ (on the fly) तयार केला जात आहे.

परंतु, ही यंत्रणा तेव्हाच काम करते जेव्हा ती वापरकर्त्याचा आदर राखते. सारख्याच अंतराचा सपाट, मेट्रोनॉमिक 'टिक-टिक-टिक' आवाज रोबोटिक वाटतो. त्याहून वाईट म्हणजे, असिस्टिव्ह टेक्नॉलॉजीकडे दुर्लक्ष करणारी अंमलबजावणी एक मजेशीर व्हिज्युअल सजावट बदलून एक त्रासदायक अडथळा बनू शकते. उद्देश वापरकर्त्याला धीमे करणे नाही; तर इंटरफेस जिवंत वाटण्यासाठी त्यात पुरेसा 'friction' (अडथळा) निर्माण करणे हा आहे.

Recursive setTimeout वापरून ते तयार करा

setTimeout पासून सुरुवात करा आणि setInterval ला स्पर्शही करू नका. हा फरक दिसण्यापेक्षा जास्त महत्त्वाचा आहे.

setInterval हट्टी आहे. तुमच्या स्क्रिप्टमध्ये किंवा ब्राउझरच्या मेन थ्रेडमध्ये काहीही घडत असले तरी ते प्रत्येक n मिलीसेकंदला फायर होते. जर तुमच्या लॉजिकला सामान्य कीस्ट्रोक दरम्यान ५० मिलीसेकंदचा विराम आणि विरामचिन्हा नंतर १५० मिलीसेकंदचा विराम हवा असेल, तर setInterval अनुकूल होऊ शकत नाही. तुम्हाला ते अतिरिक्त कंडिशनल लॉजिकमध्ये गुंडाळावे लागते, रेस कंडिशन्सशी (race conditions) लढावे लागते आणि शेवटी इतक्या वेळा इंटरव्हल क्लिअर आणि रिसेट करावा लागतो की कोड 'स्टेट-मॅनेजमेंटचा nightmare' बनतो. जेव्हा तुम्हाला बदलत्या गतीची गरज असते, तेव्हा फिक्स्ड इंटरव्हल्स अपयशी ठरतात.

Recursive setTimeout प्रत्येक पायरीला पुढच्या पायरीचे नियम स्वतः ठरवू देऊन ही समस्या सोडवते. याला एक लहान 'स्टेट मशीन' समजा. तुम्ही काही व्हेरिएबल्स राखता: सध्याचा स्ट्रिंग, सध्याचा कॅरेक्टर इंडेक्स, तुम्ही टाईप करत आहात की डिलीट करत आहात यासाठी एक बुलियन फ्लॅग, आणि एक textIndex काउंटर जेणेकरून तुम्ही एकापेक्षा जास्त स्ट्रिंग्समधून लूप करू शकाल. हे फंक्शन एक कॅरेक्टर जोडते, वाक्यात ते कुठे आहे ते तपासते आणि नंतर संदर्भाशी जुळणारा विलंब (delay) देऊन स्वतःचे पुढील इन्व्होकेशन शेड्यूल करते.

उदाहरणार्थ, तुम्ही बहुतेक कॅरेक्टर्स ५० मिलीसेकंदात टाईप करू शकता, स्वल्पविरामानंतरचा वेग १५० मिलीसेकंदपर्यंत कमी करू शकता आणि पूर्ण वाक्याच्या शेवटी डिलीट मोडमध्ये जाण्यापूर्वी ८०० मिलीसेकंदसाठी थांबू शकता. तुम्ही setInterval वापरून हे स्वच्छपणे करू शकत नाही. Recursive setTimeout सोबत, लॉजिक अगदी स्पष्ट आहे:

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

ही रचना क्लिनअप देखील सोपा करते. टाइमआउट ID साठवून ठेवा. जेव्हा घटक (component) अनमाउंट होतो किंवा वापरकर्ता दुसऱ्या पेजवर जातो, तेव्हा एकदा clearTimeout कॉल करा. बॅकग्राउंडमध्ये चालणारे कोणतेही अनाथ (orphaned) इंटरव्हल्स राहणार नाहीत.

कर्सर स्वतःहून चमकला पाहिजे

चमकणारा कर्सर ही एक व्हिज्युअल तपशील आहे, डेटाशी संबंधित बाब नाही. त्याला तुमच्या JavaScript स्टेट इंजिनमधून बाहेर ठेवा. ::after स्यूडो-एलिमेंटला जोडलेली स्वतंत्र CSS ॲनिमेशन किंवा तुमच्या टेक्स्ट कंटेनरच्या शेवटी असलेल्या समर्पित <span> चा वापर करा.

एक साधे @keyframes blink जे step-end टाइमिंगसह opacity किंवा border-color बदलते, ते तुम्हाला एक स्पष्ट, हार्डवेअर-फ्रेंडली पल्स देते जो कंपोझिटरवर चालतो. कर्सरच्या दृश्यमानतेचे सूक्ष्म व्यवस्थापन (micromanaging) करण्याचे काम JavaScript चे नाही. जर तुम्ही तुमच्या setTimeout रिकर्शनमधून डिस्प्ले प्रॉपर्टीज बदलल्या, तर तुम्ही प्रत्येक कॅरेक्टरसाठी अनावश्यक स्टाईल रिकॅल्क्युलेशन्स करण्यास भाग पाडता. सौंदर्यासाठी CSS ला काम करू द्या. क्रमाने प्रक्रिया करण्यासाठी JavaScript ला काम करू द्या.

textIndex काउंटर वापरून अनेक स्ट्रिंग्समधून लूप करणे सोपे आहे. तुमच्या स्ट्रिंग्स एका ॲरेमध्ये साठवा. जेव्हा ॲनिमेशनचा डिलीट फेज संपतो आणि कंटेनर रिकामा होतो, तेव्हा ॲरेच्या लांबीच्या मोड्युलो (modulo) textIndex वाढवा, कॅरेक्टर पॉइंटर शून्य करा आणि पुन्हा टाईप करणे सुरू करा. पोर्टफोलिओ साइट्स पेज रीलोड न करता भूमिकांमधून (उदा. ["Developer", "Designer", "Writer"]) अशा प्रकारे चक्राकार फिरतात.

भ्रम भंग करणारे चुका

अ‍ॅमॅच्युअर अंमलबजावणीमध्ये तीन चुका वारंवार दिसून येतात.

setInterval चा वापर करणे. आम्ही आधीच बदलत्या वेगाच्या (variable-speed) समस्येवर चर्चा केली आहे, परंतु एक अधिक सूक्ष्म समस्या आहे. जर तुमच्या DOM अपडेटमध्ये कधी विलंब झाला—समजा, ब्राउझर लेआउट शिफ्ट पेंट करत असल्यामुळे—तर setInterval सतत फायर होत राहते. यामुळे ओव्हरलॅपिंग राइट्स, डुप्लिकेट कॅरेक्टर्स किंवा ब्राउझर रेंडर करू शकण्यापेक्षा वेगाने होणारे राइट्स अशा समस्या उद्भवू शकतात. याउलट, रिकर्सिव्ह setTimeout पुढच्या स्टेपचा विचार करण्यापूर्वी सध्याची स्टेप पूर्ण होण्याची वाट पाहतो.

HTML एस्केप करायला विसरणे. जर तुमच्या सोर्स स्ट्रिंग्समध्ये अँगल ब्रॅकेट्स (< >) असतील आणि तुम्ही innerHTML द्वारे एक-एक अक्षर इंजेक्ट करत असाल, तर तुमचे टॅग्स अर्ध्यावर विभागले जातील. ब्राउझरला आधी <, मग <s, आणि नंतर <st असे दिसेल. यामुळे योग्य टॅग पार्सिंगला अडथळा येतो आणि तुमचे DOM नोड्स तुटलेले राहू शकतात किंवा अनपेक्षित स्टाईलिंग कॅस्केड्स (styling cascades) होऊ शकतात. जर तुम्हाला ती अक्षरे जशीच्या तशी दाखवायची असतील, तर आधी त्यांना एस्केप करा किंवा त्यापेक्षा उत्तम म्हणजे innerHTML ऐवजी textContent मध्ये लिहा. जर तुम्हाला टाईपरायटर आउटपुटमध्ये स्टाईल केलेले <span> टॅग्स खरोखरच हवे असतील, तर कॅरेक्टर लूप सुरू करण्यापूर्वी स्ट्रिंग प्री-प्रोसेस करा, जेणेकरून टॅग्स कुठे सुरू होतात आणि संपतात हे तुम्हाला नक्की समजेल.

ॲक्सेसिबिलिटीकडे (accessibility) दुर्लक्ष करणे. स्क्रीन रीडर्सना एका वेळी एक अक्षर वाचायला आवडत नाही. जसे तुमचे स्क्रिप्ट DOM मध्ये प्रत्येक नवीन अक्षर जोडते, तशी काही असिस्टिव्ह टेक्नॉलॉजीज संपूर्ण नोड पुन्हा घोषित करतात, ज्यामुळे अर्धवट शब्दांचा तुकड्या तुकड्यांत होणारा आवाज (staccato barrage) निर्माण होतो. ध्वनी-आधारित नेव्हिगेशनवर (auditory navigation) अवलंबून असलेल्या लोकांसाठी हे एक nightmare आहे. याचा उपाय गुंतागुंतीचा नाही: संपूर्ण आणि अंतिम मजकूर असलेल्या कंटेनरला एक aria-label जोडा. तुम्ही aria-hidden="true" वापरून ॲनिमेटेड एलिमेंटला असिस्टिव्ह टेकपासून पूर्णपणे लपवू शकता आणि स्क्रीन रीडर्ससाठी व्हिज्युअली हिडन स्टॅटिक कॉपी देऊ शकता. कोणत्याही परिस्थितीत, वापरकर्त्यांना तुमच्या परफॉर्मन्स मधून तासनतास जाण्यास भाग पाडण्याऐवजी त्यांना पूर्ण वाक्य आधीच उपलब्ध करून द्या.

तुमचे व्हर्जन अधिक चांगले बनवण्याचे मार्ग

एकदा मुख्य लूप व्यवस्थित काम करू लागला की, तुम्ही त्यात अतिरिक्त गोष्टी जोडू शकता. परंतु जोपर्यंत मूलभूत गोष्टी भक्कम होत नाहीत, तोपर्यंत त्या जोडण्याचा मोह टाळा.

कीस्ट्रोक साउंड इफेक्ट्स. प्रत्येक अक्षरावर येणारा एक सूक्ष्म 'क्लिक' आवाज समाधानकारक असू शकतो, परंतु वेबपेजेसवरील ऑडिओ हे एक मोठे आव्हान आहे. Web Audio API किंवा कमी बफर असलेले हलके (lightweight) Audio एलिमेंट वापरा. प्लेबॅक रेटमध्ये थोडा बदल करा—०.९५ ते १.०५ च्या दरम्यान—जेणेकरून सारखे क्लिक्स कृत्रिम वाटणार नाहीत. ब्राउझरच्या ऑटोप्ले पॉलिसीजचा नेहमी आदर करा आणि म्युट टॉगल (mute toggle) द्या. सकाळी ९ वाजता पोर्टफोलिओ पेजवर आपोआप टाईपिंगचे आवाज वाजणे, यापेक्षा वापरकर्त्यांना वेगाने दूर पळवून लावणारी दुसरी गोष्ट नाही.

मल्टी-लाइन टायपिंग. खऱ्या टर्मिनल विंडोजमध्ये मजकूर रॅप (wrap) होतो. जर तुमचा मजकूर लाइन ब्रेक ओलांडत असेल, तर तुमचा लेआउट प्रेडिक्टेबल नसेल तर साधा border-right कर्सर विचित्रपणे उडी मारेल. न्यूलाइन कॅरेक्टर्सद्वारे स्ट्रिंग्स विभाजित करा आणि प्रत्येक ओळ स्वतःच्या <span मध्ये रेंडर करा, किंवा मजकुराच्या शेवटचा मागोवा घेणारे पोझिशन केलेले pseudo-element वापरा. टेक्स्ट रॅपिंगबाबत काळजी घ्या; जर कंटेनरची रुंदी बदलली, तर इनलाइन बॉर्डर म्हणून वापरलेला कर्सर मजकुरापासून वेगळा होऊ शकतो. टर्मिनल स्टाईल्ससाठी white-space: pre-wrap आणि मोनोस्पेस फॉन्ट वापरण्याचा विचार करा, कारण फिक्स्ड-विड्थ कॅरेक्टर्समुळे कर्सरची गणिती प्रक्रिया अधिक सोपी होते.

रिअल-टाइम मार्कडाउन रेंडरिंग. इथे गोष्टी गुंतागुंतीच्या होतात. जर तुम्ही **bold** टाईप केले, तर तुमच्याकडे दोन पर्याय आहेत: ॲस्टरिक्स (asterisks) जसे आहेत तसे रेंडर करणे, किंवा त्यांना लगेच बोल्ड स्टाईलमध्ये रूपांतरित करणे. जर तुम्ही दुसरा पर्याय निवडला, तर प्रक्रियेदरम्यान textContent कडून innerHTML कडे स्विच करणे म्हणजे तुमच्या टेक्स्ट नोडच्या सीमा बदलणे होय. कर्सरची पोझिशन ठेवणे ही एक डोकेदुखी बनते कारण HTML टॅग्स तुमच्या खाली असलेल्या DOM ट्रीमध्ये बदल घडवून आणतात. एक सुरक्षित मार्ग म्हणजे कच्ची (raw) मार्कडाउन स्ट्रिंग सामान्यपणे टाईप करणे आणि पूर्ण स्ट्रिंग स्क्रीनवर आल्यावर रेंडर पास ट्रिगर करणे. जर तुम्हाला खरोखरच लाईव्ह फॉरमॅटिंग हवे असेल, तर दोन लेयर्स ठेवा: एक हिडन टाईपड बफर आणि एक पार्स केलेला व्हिज्युअल ओव्हरले.

रिव्हर्स इफेक्ट्स. मजकूर डिलीट करणे म्हणजे प्रत्येक वेळी एक-एक अक्षर बॅकस्पेस करणेच असा अर्थ नाही. तुम्ही "select all, then delete" सारखा रिसेट सिम्युलेट करू शकता, जो पुढची स्ट्रिंग टाईप होण्यापूर्वी फील्ड त्वरित साफ करतो. हे खूप यांत्रिक (clinical) वाटते. त्याऐवजी, प्रति अक्षर ३० मिलीसेकंद वेगाने बॅकस्पेसिंग केल्यास एक प्रकारचा तणाव (tension) निर्माण होतो. या दोन्ही गोष्टींचे मिश्रण करा: एखादी चूक (typo) वेगाने बॅकस्पेस करा, थोडा वेळ थांबा आणि नंतर सामान्य वेगाने डिलीट करणे सुरू करा. हा बदल त्या इफेक्टमधील मानवी स्पर्श (humanity) दर्शवतो.

मुख्य निष्कर्ष

टाईपरायटर इफेक्ट हा अशा UI घटकांपैकी एक आहे जो वरवर पाहता साधा वाटतो, परंतु तुम्ही तो बनवण्यास सुरुवात केल्यावरच त्याची जटिलता समजते. सुरुवात करा...