@@ -20,9 +20,12 @@ type Result = {
2020} ;
2121
2222export function wrapContinuouslyTypingMessage ( { root, bubble, scrollable, isEnd = false , prevPosition = - 1 } : WrapContinuouslyTypingMessageArgs ) : Result {
23- const maxPosition = getMaxPosition ( root ) ;
24-
25- const { allNodes, currentNodeIdx} = hidePrevElements ( root , prevPosition ) ;
23+ const {
24+ maxPosition,
25+ nodeContents,
26+ allNodes,
27+ currentNodeIdx
28+ } = processNodeTree ( { root, prevPosition} ) ;
2629
2730 let
2831 lastTextNode: Node ,
@@ -32,6 +35,7 @@ export function wrapContinuouslyTypingMessage({root, bubble, scrollable, isEnd =
3235
3336 function clean ( ) {
3437 cleaned = true ;
38+ allNodes . forEach ( node => nodeContents . delete ( node ) ) ;
3539 } ;
3640
3741 function onEnd ( ) {
@@ -52,10 +56,12 @@ export function wrapContinuouslyTypingMessage({root, bubble, scrollable, isEnd =
5256
5357 runAnimation ( {
5458 scrollable,
55- typeNext : ( ) => typeNext ( {
59+ typeNext : ( length ) => typeNext ( {
5660 result,
5761 setLastTextNode : ( node ) => lastTextNode = node ,
58- onEnd
62+ nodeContents,
63+ onEnd,
64+ length
5965 } ) ,
6066 isCleaned : ( ) => cleaned ,
6167 maxPosition,
@@ -66,62 +72,41 @@ export function wrapContinuouslyTypingMessage({root, bubble, scrollable, isEnd =
6672}
6773
6874
69- function getMaxPosition ( root : Node ) {
70- const initialTreeWalker = document . createTreeWalker ( root , NodeFilter . SHOW_ALL ) ;
71-
72- const initialNodes : Node [ ] = [ ] ;
73-
74- while ( initialTreeWalker . nextNode ( ) ) {
75- initialNodes . push ( initialTreeWalker . currentNode ) ;
76- }
77-
78- let position = - 1 ;
79-
80- for ( const node of initialNodes ) {
81- if ( node . nodeType === Node . TEXT_NODE ) {
82- const chars = node . textContent . split ( '' ) . map ( char => {
83- const span = document . createElement ( 'span' ) ;
84- span . textContent = char ;
85- position ++ ;
86- return span ;
87- } ) ;
88- const fragment = document . createDocumentFragment ( ) ;
89- fragment . append ( ...chars ) ;
90- node . parentNode ?. replaceChild ( fragment , node ) ;
91- }
92- }
93-
94- return position ;
95- }
96-
75+ type ProcessNodeTreeArgs = {
76+ root : Node ;
77+ prevPosition : number ;
78+ } ;
9779
98- function hidePrevElements ( root : Node , toPosition : number ) {
99- let currentNodeIdx = 0 ;
80+ function processNodeTree ( { root, prevPosition } : ProcessNodeTreeArgs ) {
81+ const treeWalker = document . createTreeWalker ( root , NodeFilter . SHOW_ALL ) ;
10082
101- const allNodesTreeWalker = document . createTreeWalker ( root , NodeFilter . SHOW_ALL ) ;
10283 const allNodes : Node [ ] = [ ] ;
84+ const nodeContents = new WeakMap < Node , string > ( ) ;
10385
104- let position = 0 ;
86+ while ( treeWalker . nextNode ( ) ) allNodes . push ( treeWalker . currentNode ) ;
10587
106- while ( allNodesTreeWalker . nextNode ( ) ) {
107- const node = allNodesTreeWalker . currentNode ;
108- allNodes . push ( node ) ;
88+ let
89+ position = - 1 ,
90+ currentNodeIdx = 0
91+ ;
10992
93+ for ( const node of allNodes ) {
11094 if ( node . nodeType === Node . TEXT_NODE ) {
111- position += node . textContent . length ;
112- } else if ( node instanceof Element && position > toPosition ) {
95+ nodeContents . set ( node , node . textContent ) ;
96+
97+ node . textContent = node . textContent . slice ( 0 , Math . max ( 0 , prevPosition - position + 1 ) ) ;
98+
99+ position += nodeContents . get ( node ) . length ;
100+ } else if ( node instanceof Element && position > prevPosition ) {
113101 node . classList . add ( styles . hidden ) ;
114102 }
115103
116- if ( position <= toPosition ) {
104+ if ( position <= prevPosition ) {
117105 currentNodeIdx ++ ;
118106 }
119107 }
120108
121- return {
122- allNodes,
123- currentNodeIdx
124- } ;
109+ return { maxPosition : position , nodeContents, allNodes, currentNodeIdx} ;
125110}
126111
127112
@@ -148,23 +133,35 @@ type TypeNextArgs = {
148133 result : Result ;
149134 setLastTextNode : ( node : Node ) => void ;
150135 onEnd : ( ) => void ;
136+ nodeContents : WeakMap < Node , string > ;
137+ length : number ;
151138} ;
152139
153- function typeNext ( { result, setLastTextNode, onEnd} : TypeNextArgs ) {
140+ function typeNext ( { result, setLastTextNode, onEnd, nodeContents , length } : TypeNextArgs ) {
154141 const { allNodes, clean} = result ;
155- result . currentPosition ++ ;
156142
157- let stillHere = true ;
158- for ( ; result . currentNodeIdx < allNodes . length && stillHere ; result . currentNodeIdx ++ ) {
143+ while ( result . currentNodeIdx < allNodes . length && length ) {
159144 const node = allNodes [ result . currentNodeIdx ] ;
160145
161146 if ( node instanceof Element ) {
147+ result . currentNodeIdx ++ ;
162148 node . classList . remove ( styles . hidden ) ;
163- }
149+ } else if ( node . nodeType === Node . TEXT_NODE ) {
150+ const typedLength = node . textContent . length ;
151+ const finalContent = nodeContents . get ( node ) ;
152+
153+ const leftOverLength = Math . max ( 0 , typedLength + length - finalContent . length ) ;
154+
155+ const start = typedLength ;
156+ const end = start + length - leftOverLength ;
157+
158+ node . textContent += finalContent . slice ( start , end ) ;
164159
165- if ( node . nodeType === Node . TEXT_NODE && node . textContent . length ) {
160+ length = leftOverLength ;
161+ result . currentPosition += end - start ;
162+
163+ if ( leftOverLength ) result . currentNodeIdx ++ ;
166164 setLastTextNode ( node ) ;
167- stillHere = false ;
168165 }
169166 }
170167
@@ -176,10 +173,10 @@ function typeNext({result, setLastTextNode, onEnd}: TypeNextArgs) {
176173
177174
178175const BASE_DELAY = 60 * 1_000 / ( 800 * 5 ) ; // 800wpm
179- const MIN_DELAY = 60 * 1_000 / ( 3_000 * 5 ) ; // 3_000wpm
176+ const MIN_DELAY = 60 * 1_000 / ( 2_400 * 5 ) ; // 2_400wpm
180177const DELAY_VARIATION = 0.3 ;
181178
182- // Try to write it with the base speed of 800wpm or burst it in 5 seconds if it's a long message, maximum speed of 3_000wpm overall
179+ // Try to write it with the base speed of 800wpm or burst it in 5 seconds if it's a long message, maximum speed of 2_400wpm overall
183180function getRandomDelay ( targetDelay : number ) {
184181 const delay = Math . max ( MIN_DELAY , Math . min ( BASE_DELAY , targetDelay ) ) ;
185182 return delay + Math . random ( ) * delay * DELAY_VARIATION ;
@@ -188,7 +185,7 @@ function getRandomDelay(targetDelay: number) {
188185
189186type RunAnimationArgs = {
190187 scrollable : HTMLElement ;
191- typeNext : ( ) => void ;
188+ typeNext : ( length : number ) => void ;
192189 isCleaned : ( ) => boolean ;
193190 maxPosition : number ;
194191 prevPosition : number ;
@@ -198,8 +195,9 @@ const TARGET_TIME_TO_WRITE = 5000;
198195
199196function runAnimation ( { scrollable, typeNext, isCleaned, maxPosition, prevPosition} : RunAnimationArgs ) {
200197 const targetDelay = TARGET_TIME_TO_WRITE / ( maxPosition - prevPosition ) ;
198+ console . log ( 'my-debug' , { maxPosition, prevPosition, targetDelay} ) ;
201199
202- let nextTime = performance . now ( ) ;
200+ let prevTime = 0 ;
203201
204202 const animationInvalidation = registerAnimationInvalidation ( scrollable ) ;
205203
@@ -211,20 +209,27 @@ function runAnimation({scrollable, typeNext, isCleaned, maxPosition, prevPositio
211209 return true ;
212210 } ;
213211
212+ let skip = - 1 ;
213+ const skipFrames = 2 ;
214+
214215 animate ( ( ) => {
215- if ( checkCleaned ( ) ) {
216- return false ;
217- }
216+ if ( checkCleaned ( ) ) return false ;
218217
218+ skip = ( skip + 1 ) % skipFrames ;
219+ if ( skip ) return true ;
219220
220221 const now = performance . now ( ) ;
222+ if ( ! prevTime ) prevTime = now ;
223+
224+ const length = Math . max ( 0 , Math . round ( ( now - prevTime ) / getRandomDelay ( targetDelay ) ) ) ;
225+ console . log ( 'my-debug' , { length} ) ;
221226
222- while ( now > nextTime && ! checkCleaned ( ) ) {
223- typeNext ( ) ;
224- nextTime = nextTime + getRandomDelay ( targetDelay ) ;
227+ if ( length ) {
228+ typeNext ( length ) ;
229+ prevTime = now ;
225230 }
226231
227- if ( ! animationInvalidation . isInvalidated ( ) ) {
232+ if ( length && ! animationInvalidation . isInvalidated ( ) ) {
228233 // value.aboutToScroll = true;
229234
230235 // animate(() => {
0 commit comments