Converting 200 lines of jQuery code into 23 lines of Svelte

Svelte is amazing. I recently converted approximately 200 lines of tangled JavaScript code from several years ago into 23 lines of Svelte.

I wanted to make a “typing” style animation, where the letters from a phrase appear after a delay. The Svelte version is so much simpler.

You can see it in action here: extrastatic.com

Typing in Svekyll

This was the end result:

  const notes = ["secure", "universal", "global", "feefree", "identity"];

  let text;
  let interval;
  let index = 0;
  let textIndex = 0;
  let wait = 0;
  onMount(() => {
    selected = notes[index];
    interval = setInterval(() => {
      if (wait > 0) {
        wait -= 1;
      } else {
        textIndex += parseInt(Math.random() * 5, 10);
        text = texts[index].slice(0, textIndex);
        if (textIndex > texts[index].length) {
          index = (index + 1) % notes.length;
          selected = notes[index];
          textIndex = 0;
          wait = 10;
        }
      }
    }, 100);
  });

  let selected;

The full Svelte template is here:

<script>
  import { onMount } from "svelte";
  import Icons from "./Icons.svelte";

  import "./finneyfor.css";

  const texts = [
    `Transactions are secure in a separate hot wallet. Your customers keep their own private keys. You don't have to add any complicated security protocols to your server (it even works with ultra secure static sites).`,
    `Collecting payments does not require your users to have anything other than a regular web browser, and it works perfectly on mobile. No special software or hardware is required.`, `Your customers can pay anywhere in the world as long as they have an internet connection. No bank account or credit card is required, and no limitations are placed on their location or nationality.`,
    `FinneyFor collects no fees. (Using a combination of smart contracts and L2 rollup technologies, fees are much smaller than $0.01 USD per transaction)`,
    `You collect money on your own site. Don't send your readers to someone else's site and risk losing your identity if you change payment providers.`,
  ];

  const notes = ["secure", "universal", "global", "feefree", "identity"];

  let text;
  let interval;
  let index = 0;
  let textIndex = 0;
  let wait = 0;
  onMount(() => {
    selected = notes[index];
    interval = setInterval(() => {
      if (wait > 0) {
        wait -= 1;
      } else {
        textIndex += parseInt(Math.random() * 5, 10);
        text = texts[index].slice(0, textIndex);
        if (textIndex > texts[index].length) {
          index = (index + 1) % notes.length;
          selected = notes[index];
          textIndex = 0;
          wait = 10;
        }
      }
    }, 100);
  });

  let selected;
</script>

<div class="grid">
  <h1 class="header text-xl pb-8">
    FinneyFor.com facilitates
    <span class:focused={selected === "secure"} class="highlight secure"
      >secure</span
    >,
    <span class:focused={selected === "universal"} class="highlight universal"
      >universal</span
    >,
    <span class:focused={selected === "global"} class="highlight global"
      >global</span
    >,
    <span class:focused={selected === "feefree"} class="highlight feeFree"
      >fee-free</span
    >
    payments between your customers and lets you fully own your
    <span class:focused={selected === "identity"} class="highlight identity"
      >identity and brand</span
    >.
  </h1>

  <div class="flex flex-row">
    <div class="p-8">
  <Icons/>
  </div>
  <div class="typing text-sm lg:text-lg">
      <span id="text" />
      <div class="">
        {text}
        <span class:myhide={index % 2 === 0}>|</span>
      </div>
    </div>
  </div>
</div>

<style>
  .typing {
    min-height: 20rem;
  }
  .myhide {
    @apply hidden;
  }
  .selected {
    @apply underline;
  }
  #text {
    @apply text-sm md:text-lg;
  }
</style>

This old version is pasted below. In addition to my 200 line jQuery based code, that index.html file included jquery itself and anime.min.js. I don’t even recall what the latter thing does.

The JavaScript uses what is defined in typing.js:

function typeIt(id) {
    const text = $((".more_" + id)).text();
    var typing=Typing(text, 20);

    // Add the icon.
    const icon = $('.more_icons .'+id).text();
    console.log( "Icon is: " + icon );
    // Make the icon
    // <i class="large material-icons">
    const iconEl = $('<i class="medium material-icons"/>').text(icon);
    $('#details #icon').html(iconEl);
    
    typing();
}

const DELAY = 6000;

function loop() {
    $('.highlight').each( (index,item) => {
	const thingy = $(item)[0];
	const calculatedDelay = DELAY*index;
	setTimeout( () => {
            thingy.classList.add('focused');
    
	    const id = thingy.classList[1];
	    //console.log( "Class list 0", 
	    //console.log( "Text is: " + text );
	    //$('.letters').html( text );
	    
			 typeIt( id ) ; //text );
	    // animateIt();
	    setTimeout( () => {
		thingy.classList.remove('focused');
	    }, (DELAY - 500));
	    console.log( "Item: ", $(item)[0].classList );
	}, calculatedDelay );
    });
    // Add two to wait two cycles...
};

$(document).ready( () => {
    loop();
    setInterval( () => {
	loop();
    }, DELAY*($('.highlight').length+2)  );
});

And, typing.js:

function Typing(string, type_speed = 100, blink_speed = 300, backspace_speed = 100, mainspanid = "text", blinkerid = "blinker") {
    return function() {
        
        let sin = string;
        let sout = "";
        let mainspan = document.getElementById(mainspanid);
        let time_order = 0;
        let blinkercontrol;
        var timeout = [];

        function startblinker() {
            blinkercontrol = setInterval(function() {
                let blinker = document.getElementById(blinkerid);
                if (blinker.style.visibility == "") {
                    blinker.style.visibility = "hidden";
                } else {
                    blinker.style.visibility = "";
                }
            }, blink_speed);
        }

        function stopblinker() {
            clearInterval(blinkercontrol);
            let blinker = document.getElementById(blinkerid);
            blinker.style.visibility = "";
        }
        for (let i = 0; i < sin.length + 1; i++) {
            if (sin[i] == "~") {
                let pause_det = calctime(i);
                pause(i, pause_det[1]);
                i = pause_det[0];
            } else if (sin[i] == "@") {
                next_line(i);
            } else if (sin[i] == "*") {
                let sen_back = wordsreplace(i);
                back_space(i, sen_back[1], backspace_speed);
                i = sen_back[0];
            } else if (sin[i] == undefined) {
                timeout.push(setTimeout(function() {
                    startblinker()
                }, time_order));
            } else {
                put(i);
            }
        }

        function calctime(a) {
            a++;
            let int = "";
            while (sin[a] != "~") {
                int = int + sin[a];
                a++;
            }
            let time = Number(int);
            if (time) {
                return [a, time];

            } else {

                catcherror("Looks like you have entered wrong time in your delay statement");
            }
        }

        function wordsreplace(a) {
            a++;
            let line = ""
            while (sin[a] != "*") {
                line = line + sin[a];
                if (sin[a] == "~" || sin[a] == "*") {
                    catcherror("Do not use ~ or * in  backspace statement");
                }
                a++;
            }
            return [a, line];
        }

        function back_space(a, text, speed) {
            a = text.length + 1;
            let index = text.length - 1;
            while (index >= 0) {
                time_order = time_order + speed;
                (function(word) {
                    timeout.push(setTimeout(function() {
                        if (word == sout[sout.length - 1]) {
                            sout = sout.substring(0, sout.length - 1);
                            mainspan.innerHTML = sout;
                        } else if (sout[sout.length - 1] == ">") {
                            sout = sout.substring(0, sout.length - 5);
                            mainspan.innerHTML = sout;
                        } else {
                            catcherror("ahh look likes your backspace statement !== actual statement");
                        }

                    }, time_order))
                })(text[index]);
                index--;
            }
        }

        function next_line(a) {
            timeout.push(setTimeout(function() {
                sout = sout + "<br>";
                mainspan.innerHTML = sout;
            }, time_order));
        }

        function pause(a, time) {
            timeout.push(setTimeout(function() {
                startblinker();
            }, time_order))
            timeout.push(setTimeout(function() {
                stopblinker();
            }, time_order + time));
            time_order = time_order + time;
        }

        function put(a) {
            time_order = time_order + (type_speed)
            timeout.push(setTimeout(function() {
                sout = sout + sin[a];
                mainspan.innerHTML = sout;
            }, time_order));
        }

        function catcherror(errormsg) {
            for (let c = 0; c < timeout.length; c++) {
                clearTimeout(timeout[c]);
            }
            throw new Error(errormsg);

        }
    }
}