<div id="ccc"></div>

<div id="config">
  <div class="config-wrap">
    <label
      id="num-particles-label"
      for="num-particles"
    >
      Num Particles (200)
    </label>
    <input
      type="range"
      min="50"
      max="400"
      id="num-particles"
      name="num-particles"
      value="200"
    />
  </div>
  <div class="config-wrap">
    <label
      id="line-width-label"
      for="line-width"
    >
      Line width (1)
    </label>
    <input
      type="range"
      min="1"
      max="20"
      id="line-width"
      name="line-width"
      value="1"
    />
  </div>
  <div class="config-wrap">
    <label
      id="min-dist-label"
      for="min-dist"
    >
      Min dist (50)
    </label>
    <input
      type="range"
      min="1"
      max="100"
      step="1"
      id="min-dist"
      name="min-dist"
      value="50"
    />
  </div>
  <div class="config-wrap">
    <label
      id="stroke-color-label"
      for="stroke-color"
    >
      Stroke Color (#2980b9)
    </label>
    <input
      type="color"
      min="1"
      max="20"
      step="1"
      id="stroke-color"
      name="stroke-color"
      value="#2980b9"
    />
  </div>
  <div class="config-wrap">
    <label
      id="fill-color-label"
      for="fill-color"
    >
      Fill Color (#2980b9)
    </label>
    <input
      type="color"
      min="1"
      max="20"
      step="1"
      id="fill-color"
      name="fill-color"
      value="#2980b9"
    />
  </div>
</div>

<script id="c" type="text/worklet">
  const PAINTLET_NAME = 'dots-connections'

  class CSSPaintlet {
    static get inputProperties() {
      return [
        `--${PAINTLET_NAME}-line-width`,
        `--${PAINTLET_NAME}-stroke-color`,
        `--${PAINTLET_NAME}-fill-color`,
        `--${PAINTLET_NAME}-connection-min-dist`,
        `--${PAINTLET_NAME}-count`,
      ]
    }

    paint(ctx, paintSize, props, args) {
      const lineWidth = Number(props.get(`--${PAINTLET_NAME}-line-width`))
      const minDist = Number(props.get(`--${PAINTLET_NAME}-connection-min-dist`))
      const strokeColor = props.get(`--${PAINTLET_NAME}-stroke-color`)
      const fillColor = props.get(`--${PAINTLET_NAME}-fill-color`)
      const numParticles = Number(props.get(`--${PAINTLET_NAME}-count`))
      
      console.log(props)
      
      const particles = new Array(numParticles).fill(null).map(_ => ({
        x: Math.random() * paintSize.width,
        y: Math.random() * paintSize.height,
        radius: 2 + Math.random() * 2,
      }))
      
      ctx.lineWidth = lineWidth
      ctx.lineJoin = 'round'
      ctx.lineCap = 'round'
      
      for (let i = 0; i < numParticles; i++) {
        const particle = particles[i]
        for (let n = 0; n < numParticles; n++) {
          if (i === n) {
            continue
          }
          const nextParticle = particles[n]
          const dx = nextParticle.x - particle.x
          const dy = nextParticle.y - particle.y
          const dist = Math.sqrt(dx * dx + dy * dy)
          if (dist < minDist) {
            ctx.strokeStyle = strokeColor
            ctx.beginPath()
            ctx.moveTo(nextParticle.x, nextParticle.y)
            ctx.lineTo(particle.x, particle.y)
            ctx.stroke()
          }
        }
        
        ctx.fillStyle = fillColor
        ctx.beginPath()
        ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2)
        ctx.closePath()
        ctx.fill()
      }
      
    }
  }

  registerPaint(PAINTLET_NAME, CSSPaintlet)
</script>
* {
  margin: 0;
  padding: 0;
  font-family: Helvetica, sans-serif;
}

#ccc {
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  
  --dots-connections-line-width: 1;
  --dots-connections-stroke-color: #2980b9;
  --dots-connections-fill-color: #2980b9;
  --dots-connections-connection-min-dist: 50;
  --dots-connections-count: 200;

  background-image: paint(dots-connections);
  background-repeat: no-repeat;
  background-size: cover;
 
}

#config {
  position: fixed;
  bottom: 0;
  left: 0;
  padding: 1rem;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(3px);
  box-sizing: border-box;
  border-top-right-radius: 24px;
}

.config-wrap {
  margin-bottom: 0.75rem;
  color: rgba(0, 0, 0, 0.8);
}

label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  margin-bottom: 0.5rem;
}
console.clear()

const paintletCode = document.getElementById('c')

;(async function() {
  // ⚠️ Handle Firefox and Safari by importing a polyfill for CSS Pain    
  if (CSS['paintWorklet'] === undefined) {
    await import('https://unpkg.com/css-paint-polyfill')
  }

  // Explicitly define our custom CSS variable
  // Make sure that the browser treats it as a number
  // It does not inherit it's value
  // It's initial value defaults to 1
  if ('registerProperty' in CSS) {
    CSS.registerProperty({
      name: '--dots-connections-line-width',
      syntax: '<number>',
      inherits: false,
      initialValue: 10
    })
    CSS.registerProperty({
      name: '--dots-connections-stroke-color',
      syntax: '<color>',
      inherits: false,
      initialValue: '#2980b9'
    })
    CSS.registerProperty({
      name: '--dots-connections-fill-color',
      syntax: '<color>',
      inherits: false,
      initialValue: '#2980b9'
    })
    CSS.registerProperty({
      name: '--dots-connections-connection-min-dist',
      syntax: '<number>',
      inherits: false,
      initialValue: 50
    })
  }
  
  const numParticlesInput = document.getElementById('num-particles')
  const lineWidthInput = document.getElementById('line-width')
  const minDistInput = document.getElementById('min-dist')
  const strokeInput = document.getElementById('stroke-color')
  const fillInput = document.getElementById('fill-color')
  
  const numParticlesLabel = document.getElementById('num-particles-label')
  const lineWidthLabel = document.getElementById('line-width-label')
  const minDistLabel = document.getElementById('min-dist-label')
  const strokeLabel = document.getElementById('stroke-color-label')
  const fillLabel = document.getElementById('fill-color-label')
  
  const workletElement = document.getElementById('ccc')
  
  numParticlesInput.addEventListener('change', e => {
    const numParticles = Number(e.target.value)
    numParticlesLabel.textContent = `Num Particles (${numParticles})`
    workletElement.style.setProperty('--dots-connections-count', numParticles)
  })
  lineWidthInput.addEventListener('change', e => {
    const value = Number(e.target.value)
    lineWidthLabel.textContent = `Line width (${value})`
    workletElement.style.setProperty('--dots-connections-line-width', value)
  })
  minDistInput.addEventListener('change', e => {
    const value = Number(e.target.value)
    minDistLabel.textContent = `Min Dist (${value})`
    workletElement.style.setProperty('--dots-connections-connection-min-dist', value)
  })
  strokeInput.addEventListener('change', e => {
    strokeLabel.textContent = `Stroke Color (${e.target.value})`
    workletElement.style.setProperty('--dots-connections-stroke-color', e.target.value)
  })
  fillInput.addEventListener('change', e => {
    fillLabel.textContent = `Fill Color (${e.target.value})`
    workletElement.style.setProperty('--dots-connections-fill-color', e.target.value)
  })

  // Include our paintlet using
  registerCSSPaintlet(paintletCode.textContent)
})()

function registerCSSPaintlet (sourceCode) {  CSS.paintWorklet.addModule(`data:application/javascript;charset=utf8,${encodeURIComponent(sourceCode)}`)
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.