DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

4 3

Open Source Adventures: Episode 44: Spooky Eyes in SolidJS

Let's code a very simple app in SolidJS, Spooky Eyes! Here's what Svelte version looks like.

The app is simple - there's a bunch of randomly placed eyes, which follow the mouse.

I covered basic structure of the project in previous episode, here let's just focus on the interesting bits.

index.css

SolidJS has support for component scoped CSS, but for simplicity I put everything in a static component.

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  text-align: center;
  justify-content: center;
  align-items: center;
}
svg {
  width: 100vw;
  height: 100vh;
  display: block;
  background-color: #aaa;
}
.eye1 {
  fill: white;
  stroke: black;
  stroke-width: 3px;
}
.eye2 {
  stroke: black;
  stroke-width: 1px;
}
.eye3 {
  fill: black;
}
Enter fullscreen mode Exit fullscreen mode

App.solid

The App component, when created, initializes a list of randomly placed Eyes. It also tracks where the mouse is, and passes it to Eye child instances.

import Eye from "./Eye"
import { createSignal } from "solid-js"

function randomColor() {
  let h = Math.random() * 360
  let s = Math.round(50 + Math.random() * 50)
  let l = Math.round(30 + Math.random() * 40)
  return `hsl(${h}, ${s}%, ${l}%)`
}

function eyeDistance(eye1, eye2) {
  let dx = eye1.x - eye2.x;
  let dy = eye1.y - eye2.y;
  return Math.sqrt((dx * dx) + (dy * dy))
}

function canPlaceEye(eyes, newEye) {
  return eyes.every(eye =>
    eyeDistance(eye, newEye) >= eye.sz + newEye.sz + 5
  )
}

function generateEyes() {
  let eyes = []
  let wh = window.innerHeight
  let ww = window.innerWidth
  let mx = Math.random() * ww
  let my = Math.random() * wh
  for(let i=0; i<1000; i++) {
    let sz = 20 + Math.random() * 60
    let x = sz + Math.random() * (ww - 2 * sz)
    let y = sz + Math.random() * (wh - 2 * sz)
    let color = randomColor()
    let newEye = {x, y, sz, color}
    if (canPlaceEye(eyes, newEye)) {
      eyes.push(newEye)
    }
  }
  return eyes
}

function App() {
  let eyes = generateEyes()
  let wh = window.innerHeight
  let ww = window.innerWidth
  // init it to something random if mouse starts outside window
  let [mx, setMx] = createSignal(Math.random() * ww)
  let [my, setMy] = createSignal(Math.random() * wh)

  function onMouseMove() {
    let svg = document.getElementById("eyes")
    let rect = svg.getBoundingClientRect()
    setMx(event.pageX - rect.x)
    setMy(event.pageY - rect.y)
  }

  return (
    <svg id="eyes" onMouseMove={onMouseMove}>
      <For each={eyes}>
        {({x, y, sz, color}) => <Eye x={x} y={y} sz={sz} color={color} mx={mx} my={my} />}
      </For>
    </svg>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Some notable things:

  • it's JSX, but unlike React we don't loop with .map, we need to use <For>...</For>. Most frameworks except React end up having special syntax for looping, if/else etc.
  • we have two parts of state - mx and my mouse position. eyes are static so they don't need to be createSignaled

Other than that, it is quite close to what we'd do in React, so learning curve for React developers should be pretty low.

Eye.solid

Here's Eye component:

import { createSignal, createEffect } from "solid-js"

function Eye({x,y,sz,color,mx,my}) {
  let [rx, setRx] = createSignal(x)
  let [ry, setRy] = createSignal(y)

  createEffect(() => {
    let max_eye_movement = 0.3 * sz
    let dx = mx() !== null ? (mx() - x) : 0
    let dy = my() !== null ? (my() - y) : 0
    let dl = Math.max(0.000001, Math.sqrt(dx*dx + dy*dy))
    let displacement = Math.min(max_eye_movement, dl)
    setRx(x + dx/dl * displacement)
    setRy(y + dy/dl * displacement)
  })

  return (
    <g>
      <circle class="eye1" cx={x} cy={y} r={sz} />
      <circle class="eye2" cx={rx()} cy={ry()} r={sz * 0.5} style={`fill: ${color}`}/>
      <circle class="eye3" cx={rx()} cy={ry()} r={sz * 0.2}/>
    </g>
  )
}

export default Eye
Enter fullscreen mode Exit fullscreen mode

This is quite awkward. In React we wouldn't need any state, and any effects. We'd just take mx and my as props, calculate everything, and return the result.

Solid requires being a lot more explicit. Arguably you gain some performance, at cost of a lot of extra work.

Arguably it looks more or less like React code - Recat developer would understand what's going on - it would just be somewhat unclear why these choices were made.

Story so far

I deployed this on GitHub Pages, you can see it here. It works very smoothly, and quite a few frameworks I tested already struggle with this app.

So far my impressions of SolidJS aren't amazing. Its reactivity requires a lot more work from developer than React or Svelte, and I don't think it's worth it for me.

I can definitely see scenarios where tradeoffs SolidJS offers make sense. It looks like React, so for masses of React developers, it should be much easier to switch to than Svelte. Without virtual DOM overhead, it should perform roughly comparable to Svelte, and a lot better than React (in principle; I didn't benchbark anything). And it's all runtime so unlike Svelte, it doesn't require any tooling beyond just JSX that's supported everywhere.

But overall I think I'll stick to Svelte for my projects.

Coming next

In the next few episodes, we'll do some video game data analysis.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay