<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Evenilson Liandro</title>
    <description>The latest articles on DEV Community by Evenilson Liandro (@evenilsonliandro).</description>
    <link>https://dev.to/evenilsonliandro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3251411%2F3941aeee-8fd4-4488-b6e3-3684780bee97.jpg</url>
      <title>DEV Community: Evenilson Liandro</title>
      <link>https://dev.to/evenilsonliandro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/evenilsonliandro"/>
    <language>en</language>
    <item>
      <title>Efeito de máquina de escrever com React usando requestAnimationFrame</title>
      <dc:creator>Evenilson Liandro</dc:creator>
      <pubDate>Mon, 16 Jun 2025 16:48:05 +0000</pubDate>
      <link>https://dev.to/evenilsonliandro/efeito-de-maquina-de-escrever-com-react-usando-requestanimationframe-2fep</link>
      <guid>https://dev.to/evenilsonliandro/efeito-de-maquina-de-escrever-com-react-usando-requestanimationframe-2fep</guid>
      <description>&lt;p&gt;Quando navegamos em sites com apelo visual — portfólios, &lt;em&gt;landing pages&lt;/em&gt;, &lt;em&gt;hero sections&lt;/em&gt; — é comum vermos efeitos de texto digitando automaticamente, como uma máquina de escrever. Embora pareça simples, muitos exemplos ainda usam &lt;code&gt;setTimeout&lt;/code&gt; ou &lt;code&gt;setInterval&lt;/code&gt; para isso.&lt;/p&gt;

&lt;p&gt;Mas se buscamos algo mais &lt;strong&gt;preciso, performático e responsivo&lt;/strong&gt;, o &lt;code&gt;requestAnimationFrame&lt;/code&gt; é o caminho certo.&lt;/p&gt;




&lt;h2&gt;
  
  
  O problema de &lt;code&gt;setTimeout&lt;/code&gt; ou &lt;code&gt;setInterval&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Essas abordagens funcionam, mas têm &lt;strong&gt;sérias desvantagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Não são sincronizadas com o &lt;em&gt;render&lt;/em&gt; do navegador&lt;/li&gt;
&lt;li&gt;Continuam rodando mesmo se a aba estiver em segundo plano&lt;/li&gt;
&lt;li&gt;Consomem mais CPU em animações longas&lt;/li&gt;
&lt;li&gt;Podem criar efeitos instáveis se o usuário mudar de aba ou se a aplicação for pesada&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Usar &lt;code&gt;requestAnimationFrame&lt;/code&gt; é o caminho
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;requestAnimationFrame&lt;/code&gt; é uma API nativa do navegador feita para isso. Ele:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executa o código &lt;strong&gt;antes de cada frame&lt;/strong&gt; ser renderizado&lt;/li&gt;
&lt;li&gt;Suspende automaticamente quando a aba fica em segundo plano&lt;/li&gt;
&lt;li&gt;Garante maior precisão temporal e fluidez&lt;/li&gt;
&lt;li&gt;É a base de qualquer animação moderna, como &lt;code&gt;Framer Motion&lt;/code&gt;, &lt;code&gt;GSAP&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  O &lt;em&gt;hook&lt;/em&gt; &lt;code&gt;useTypeWriter&lt;/code&gt; com React
&lt;/h2&gt;

&lt;p&gt;Vamos criar um hook chamado &lt;code&gt;useTypeWriter&lt;/code&gt;, que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Digita e apaga uma lista de frases&lt;/li&gt;
&lt;li&gt;Permite configurar a velocidade de digitação e remoção&lt;/li&gt;
&lt;li&gt;Suporta &lt;em&gt;loop&lt;/em&gt; e pausas entre as frases&lt;/li&gt;
&lt;li&gt;Usa apenas React e &lt;code&gt;requestAnimationFrame&lt;/code&gt; (sem &lt;em&gt;libs&lt;/em&gt; externas)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UseTypeWriterProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;writeSpeed&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eraseSpeed&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pauseBeforeDelete&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pauseBetweenPhrases&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onCycleComplete&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useTypeWriter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;writeSpeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;eraseSpeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pauseBeforeDelete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pauseBetweenPhrases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onCycleComplete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;UseTypeWriterProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;displayed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDisplayed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Current visible text&lt;/span&gt;

  &lt;span class="c1"&gt;// Refs to control typing state without causing re-renders&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animationFrameRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ID from requestAnimationFrame &lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastFrameTimeRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// Timestamp of the last frame&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;charIndexRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// Current character index&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;phraseIndexRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;               &lt;span class="c1"&gt;// Current phrase index   &lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDeletingRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// Whether we are currently deleting&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pauseUntilRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// Pause between transitions&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isCancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Cancel any ongoing animation before starting a new one&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;phraseIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Handle pauses (e.g., between typing and deleting)&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pauseUntilRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pauseUntilRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastFrameTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isDeletingRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;eraseSpeed&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;writeSpeed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Continue only if the delay time has passed&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isDeletingRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Typing mode&lt;/span&gt;
          &lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nf"&gt;setDisplayed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

          &lt;span class="c1"&gt;// Reached the end of the phrase&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;isDeletingRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;pauseUntilRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pauseBeforeDelete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Wait before deleting&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Deleting mode&lt;/span&gt;
          &lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nf"&gt;setDisplayed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

          &lt;span class="c1"&gt;// Finished deleting&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;isDeletingRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;phraseIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextIndex&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;phraseIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;onCycleComplete&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt; &lt;span class="c1"&gt;// Notify parent&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Stop animation&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;phraseIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nextIndex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;charIndexRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;pauseUntilRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pauseBetweenPhrases&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Pause briefly before typing the next phrase&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;lastFrameTimeRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Update last action time&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Keep animation going&lt;/span&gt;
      &lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Start animation loop&lt;/span&gt;
    &lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Clean up on unmount or re-run&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isCancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;cancelAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animationFrameRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;writeSpeed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eraseSpeed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onCycleComplete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pauseBeforeDelete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pauseBetweenPhrases&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;displayed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Exemplos de uso
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useTypeWriter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./hooks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTypeWriter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome to my site.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;writeSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;eraseSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-zinc-900 w-full h-screen flex items-center justify-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-zinc-100 text-7xl"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Esse &lt;em&gt;hook&lt;/em&gt; é muito bom para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Hero sections&lt;/em&gt; de portfólios&lt;/li&gt;
&lt;li&gt;Interfaces de &lt;em&gt;onboarding&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Aplicações com personalidade e movimento
E sem depender de &lt;em&gt;libs&lt;/em&gt; externas. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Curtiu o &lt;em&gt;hook&lt;/em&gt;? Veja o código no GitHub e compartilhe com alguém que curte animações em React:&lt;br&gt;
&lt;a href="//github.com/evenilson/use-typewriter"&gt;github.com/evenilson/use-typewriter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>frontend</category>
      <category>typescript</category>
      <category>hooks</category>
    </item>
    <item>
      <title>Como criar um scrollspy moderno com React e IntersectionObserver</title>
      <dc:creator>Evenilson Liandro</dc:creator>
      <pubDate>Wed, 11 Jun 2025 18:16:32 +0000</pubDate>
      <link>https://dev.to/evenilsonliandro/como-criar-um-scrollspy-moderno-com-react-e-intersectionobserver-5ap8</link>
      <guid>https://dev.to/evenilsonliandro/como-criar-um-scrollspy-moderno-com-react-e-intersectionobserver-5ap8</guid>
      <description>&lt;p&gt;Quando navegamos em &lt;em&gt;landing pages&lt;/em&gt; modernas, é comum vermos o menu se adaptando automaticamente conforme o usuário rola a página. Esse efeito é conhecido como &lt;em&gt;&lt;strong&gt;scrollspy&lt;/strong&gt;&lt;/em&gt;, e melhora a experiência de navegação, além de dar um toque profissional ao site.&lt;/p&gt;

&lt;p&gt;Neste artigo, vamos demonstrar como implementar um &lt;em&gt;&lt;strong&gt;scrollspy&lt;/strong&gt;&lt;/em&gt; moderno utilizando React e a API nativa &lt;code&gt;IntersectionObserver&lt;/code&gt;, garantindo precisão, performance e manutenção limpa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por que devemos evitar &lt;code&gt;scrollY&lt;/code&gt; e &lt;code&gt;offsetTop&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;window.scrollY&lt;/code&gt; ainda é comumente utilizado, geralmente em conjunto com &lt;code&gt;element.offsetTop&lt;/code&gt; ou &lt;code&gt;getBoundingClientRect()&lt;/code&gt;. Embora funcionem, essas abordagens apresentam desvantagens importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exigir cálculos manuais e constantes;&lt;/li&gt;
&lt;li&gt;Precisam de &lt;em&gt;event listeners&lt;/em&gt; em &lt;code&gt;scroll&lt;/code&gt; (impactando a performance);&lt;/li&gt;
&lt;li&gt;Reagem mal a mudanças de layout ou responsividade.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Utilizando o &lt;code&gt;IntersectionObserver&lt;/code&gt;, podemos observar elementos e reagir automaticamente à visibilidade real deles na tela. Ele notifica o navegador quando um elemento entra ou sai da tela, com menos código e mais eficiência, já que foi projetado exatamente para esse tipo de tarefa.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  O IntersectionObserver
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; é uma API nativa do navegador que permite detectar quando um elemento entra ou sai da área visível (viewport).&lt;/p&gt;

&lt;h3&gt;
  
  
  Quando utilizar:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Scrollspy&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Lazy loading&lt;/em&gt; de imagens&lt;/li&gt;
&lt;li&gt;Animações disparadas no &lt;em&gt;scroll&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Análise de comportamento do usuário&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exemplo simples de como a API funciona
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const observer = new IntersectionObserver((entries) =&amp;gt; {
    entries.forEach((entry) =&amp;gt; {
        if(entry.isIntersecting) {
            console.log(`${entry.target.id} está visível`);
        }
    });
});

observer.observe(document.getElementById("home"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;O código acima começa a observar a seção com &lt;code&gt;id="home"&lt;/code&gt; e imprime no console quando ela se torna visível na tela.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Criação do &lt;em&gt;hook&lt;/em&gt; &lt;code&gt;useActiveSection&lt;/code&gt; com React
&lt;/h2&gt;

&lt;p&gt;Aqui o objetivo é criar um &lt;em&gt;hook&lt;/em&gt; reutilizável responsável por detectar qual seção da página está mais visível. Isso será usado para destacar automaticamente o item ativo no menu.&lt;/p&gt;

&lt;h3&gt;
  
  
  Como funciona
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Recebe &lt;em&gt;refs&lt;/em&gt; para as seções;&lt;/li&gt;
&lt;li&gt;Usa o &lt;code&gt;IntersectionObserver&lt;/code&gt; para monitorar visibilidade;&lt;/li&gt;
&lt;li&gt;Calcula a área visível real de cada seção;&lt;/li&gt;
&lt;li&gt;Atualiza o estado com a seção mais visível;&lt;/li&gt;
&lt;li&gt;Utiliza &lt;code&gt;MutationObserver&lt;/code&gt; para aguardar dinamicamente o momento em que as &lt;em&gt;refs&lt;/em&gt; se tornam disponíveis.&lt;/li&gt;
&lt;li&gt;Utiliza &lt;code&gt;RequestAnimationFrame&lt;/code&gt; para garantir que o DOM esteja completamente renderizado antes de inicializar o &lt;code&gt;IntersectionObserver&lt;/code&gt;. Isso é essencial para compatibilidade com React 18+, que utiliza o modo &lt;code&gt;Strict&lt;/code&gt; em desenvolvimento e pode montar componentes duas vezes.&lt;/li&gt;
&lt;li&gt;(Opcional) Utiliza &lt;code&gt;requestAnimationFrame&lt;/code&gt; como &lt;em&gt;fallback&lt;/em&gt; para garantir a ativação correta da primeira seção visível. Útil em cenários onde o conteúdo estático não dispara o &lt;code&gt;IntersectionObserver&lt;/code&gt; imediatamente, como após SSR ou renderização instantânea de várias seções fora do &lt;em&gt;viewport&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Código do &lt;em&gt;hook&lt;/em&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useEffect, useRef, useState } from "react";

interface SectionRefMap {

}

export function useActiveSection(sectionRefs: SectionRefMap, rootMargin = "-40% 0px -40% 0px", enableFallback = true) {
  const [activeSection, setActiveSection] = useState&amp;lt;string | null&amp;gt;(null);

  const observer = useRef&amp;lt;IntersectionObserver | null&amp;gt;(null);
  const currentId = useRef&amp;lt;string | null&amp;gt;(null);
  const hasInitialized = useRef(false);
  const fallbackRafId = useRef&amp;lt;number | null&amp;gt;(null);

  useEffect(() =&amp;gt; {
    const refEntries = Object.entries(sectionRefs);
    const allAvailableSections = refEntries.every(([, ref]) =&amp;gt; ref.current !== null);

    // Prevent multiple initializations
    if (hasInitialized.current) return;

    if (allAvailableSections) {
      requestAnimationFrame(() =&amp;gt; {
        if (!hasInitialized.current) {
          initializeObserver();
        }
      })
    } else {
      // Wait until all refs are assigned
      const mo = new MutationObserver(() =&amp;gt; {
        const ready = refEntries.every(([, ref]) =&amp;gt; ref.current !== null);
        if (ready) {
          mo.disconnect();
          requestAnimationFrame(() =&amp;gt; {
            if (!hasInitialized.current) {
              initializeObserver();
            }
          })
        }
      });
      mo.observe(document.body, {
        childList: true,
        subtree: true,
      });
      return () =&amp;gt; mo.disconnect();
    }

    function initializeObserver() {
      if(hasInitialized.current) return;
      hasInitialized.current = true;
      // Create observer to track visible sections
      observer.current = new IntersectionObserver(
        (observerEntries) =&amp;gt; {
          const visibleEntries = observerEntries
            .filter(entry =&amp;gt; entry.isIntersecting)
            .map((entry) =&amp;gt; ({
              entry,
              area:
                entry.intersectionRect.width * entry.intersectionRect.height,
            }))
            .sort((a, b) =&amp;gt; b.area - a.area);

          // Set the section with the largest visible area
          if (visibleEntries.length &amp;gt; 0) {
            const id = visibleEntries[0].entry.target.getAttribute("id");
            if (id &amp;amp;&amp;amp; id !== currentId.current) {
              currentId.current = id;
              setActiveSection(id);
            }
          }
        },
        {
          rootMargin,
          threshold: Array.from({ length: 11 }, (_, i) =&amp;gt; i / 10),
        }
      );

      // Start observing all section elements
      for (const [, ref] of refEntries) {
        if (ref.current) {
          observer.current.observe(ref.current);
        }
      }

      // Optional: fallback using requestAnimationFrame to detect visible sections on first render
      if (enableFallback) {
        fallbackRafId.current = requestAnimationFrame(() =&amp;gt; {
          const visibleSections = refEntries
            .map(([key, ref]) =&amp;gt; {
              if (!ref.current) return null;
              const rect = ref.current.getBoundingClientRect();
              const visibleHeight = Math.max(0, Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0));
              const visibleWidth = Math.max(0, Math.min(rect.right, window.innerWidth) - Math.max(rect.left, 0));
              const area = visibleHeight * visibleWidth;
              return { key, area };
            })
            .filter(Boolean)
            .sort((a, b) =&amp;gt; b!.area - a!.area);

          if (visibleSections.length &amp;gt; 0) {
            const firstVisibleSection = visibleSections[0]!;
            if (firstVisibleSection?.key !== currentId.current) {
              currentId.current = firstVisibleSection.key;
              setActiveSection(firstVisibleSection.key);
            }
          }
        });
      }
    }

    // Cleanup: unobserve all sections
    return () =&amp;gt; {
      if(observer.current) {
        for (const [, ref] of refEntries) { 
          if (ref.current &amp;amp;&amp;amp; observer.current) {
            observer.current.unobserve(ref.current);
          }
        }
        observer.current?.disconnect();
        observer.current = null;
      }
      if(fallbackRafId.current !== null) {
        cancelAnimationFrame(fallbackRafId.current);
        fallbackRafId.current = null;
      }
    };
  }, [sectionRefs, rootMargin]);

  return activeSection;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Como usar
&lt;/h3&gt;

&lt;p&gt;No componente Header ou outro, pode-se usar o hook assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const homeRef = useRef&amp;lt;HTMLElement&amp;gt;(null!);
const aboutRef = useRef&amp;lt;HTMLElement&amp;gt;(null!);
const projectsRef = useRef&amp;lt;HTMLElement&amp;gt;(null!);
const contactRef = useRef&amp;lt;HTMLElement&amp;gt;(null!);

const sectionRefs = useMemo(() =&amp;gt; ({
    home: homeRef,
    about: aboutRef,
    projects: projectsRef,
    contact: contactRef,
}), []);

const activeSection = useActiveSection(sectionRefs);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;useMemo&lt;/code&gt; é usado aqui para garantir que o objeto &lt;code&gt;sectionRefs&lt;/code&gt; não seja recriado em cada renderização, o que evita reexecutar o efeito do &lt;em&gt;hook&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E devemos destacar o item ativo no menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a
 href="#home"
 className={activeSection === "home" ? "active" : ""}
&amp;gt;
   Início
&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefícios
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Alta performance (sem &lt;em&gt;polling&lt;/em&gt;);&lt;/li&gt;
&lt;li&gt;Robusto mesmo com conteúdo assíncrono;&lt;/li&gt;
&lt;li&gt;Reutilizável com qualquer estrutura de seções;&lt;/li&gt;
&lt;li&gt;Preciso, mesmo com animações ou conteúdo que muda dinamicamente;&lt;/li&gt;
&lt;li&gt;Ideal para &lt;em&gt;landing pages&lt;/em&gt;, documentações e &lt;em&gt;single-page apps&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;O &lt;code&gt;IntersectionObserver&lt;/code&gt; é uma ferramenta poderosa. Ao ser combinada com React, permite criar experiências modernas, performáticas e responsivas. Se você está com vontade de sair da bolha do &lt;code&gt;scrollY&lt;/code&gt; e entrar no mundo dos eventos inteligentes, esse é o caminho.&lt;/p&gt;

&lt;p&gt;Se você curtiu o artigo, compartilhe com outros colegas desenvolvedores e siga para mais conteúdo como este!&lt;/p&gt;

&lt;p&gt;Veja o código no &lt;a href="https://github.com/evenilson/react-intersection-scrollspy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conteúdos relacionados
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" rel="noopener noreferrer"&gt;Intersection Observer API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://legacy.reactjs.org/docs/refs-and-the-dom.html" rel="noopener noreferrer"&gt;React Refs - React Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noopener noreferrer"&gt;MutationObserver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>intersectionobserver</category>
      <category>scrollspy</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
