How to animate growing spiral with different colors?

I have an application which animates spiral with music on pure HTML Canvas (by using lines). Here is the animation :

I want to start using three.js for this animation, and I’ve just started learning it.

Here is the code, with which I try to recreate that animation, but I don’t think is the optimized way.

What is the best way to achieve the animation ?

const drawLine = () => {
  const points = [];
  points.push(new THREE.Vector3(x, y, z));
  const LINE_DISTANCE = 0.003;
  x = (r + Math.random()) * Math.cos(tetha);
  y = (r + Math.random()) * Math.sin(tetha);
  z += 0.002;
  tetha += 0.015;
  points.push(new THREE.Vector3(x, y, z));
  const geometry = new THREE.BufferGeometry().setFromPoints(points);
  const color = new THREE.Color(0xffffff);
  color.setHex(Math.random() * 0xffffff);
  const material = new THREE.LineBasicMaterial({ color });
  const line = new THREE.Line(geometry, material);

function animate() {
  renderer.render(scene, camera);

You want to avoid creating objects in the render loop as much as possible, as they will be garbage collected at some point which can slow down the rendering.
Also note that creating a new line object for each line is inefficient. You could use a single geometry and material for all of them. The geometry would contain all the points, and you would need to create a vertex color attribute to hold the colors, then set verteColors: true in your material’s parameters.

I would create the whole geometry when you’re starting the application. In the update loop, increment the geometry’s draw range: this will let you progressively reveal new segments.

Thank you for the response. I will start researching how to make things you mentioned work (vertexColors, geometry, etc.).

I researched that options, but still I can’t avoid creating objects in loop, because user can change spiral animation parameters in real-time, which includes coordinates and line width, hence I can’t use single geometry.
But what I do is, after drawing specific numbers of lines (300 for now), I delete all 300 of them and create one single geometry with different colors, and so on. The solution works better than my previous code which I have in the question above.

The problem is, sometimes animation slows down, and sometimes fastens. I tried using requestAnimationFrame, as well as setTimeout within the animate function.

Any ideas how to optimize the code ?

Here’s the website where I develop the app. You can check it out yourself :
Just click upload to upload the audio file or start microphone recording and the animation will start.

Here’s my current code :

    const drawLine = () => {
        const { color, sample } = getCurrentData(currentTime.current);
        const points = [];
        points.push(new THREE.Vector3(x.current, y.current, z.current));
        lastGraphicsData.current.coordinates.push(x.current, y.current, z.current);
        const LINE_DISTANCE = getLineDistance(sample, tethaIncreaser.current) * LINE_DISTANCE_INDEX;
        sampleSpike.current = getSampleSpike(sample, WAVE_SPIKE_INDEX);
        if (r.current + sampleSpike.current > maximumR.current) {
          maximumR.current = r.current + sampleSpike.current;
        r.current += LINE_DISTANCE;
        x.current = (r.current + sampleSpike.current) * Math.cos(tetha.current);
        y.current = (r.current + sampleSpike.current) * Math.sin(tetha.current);
        tetha.current -= tethaIncreaser.current;
        points.push(new THREE.Vector3(x.current, y.current, z.current));
        const geometry = new THREE.BufferGeometry().setFromPoints(points);
        const material = new THREE.LineBasicMaterial({ color, linewidth: LINE_WIDTH });
        const line = new THREE.Line(geometry, material);
        renderer.render(scene, camera);


    const animate = () => {
      setTimeout(animate, DRAWING_INTERVAL_MS);
      if (lastGraphicsData.current.objects.length === 300) replaceLinesWithOneBigLine(lastGraphicsData, scene);


You can modify a buffer geometry after it’s been created, by modifying the relevant attributes array and setting needsUpdate = true on the attribute. So there’s no need to create a new geometry for each line - this is probably where your performance issues are coming from.

There are two different scenarios:

  • the user uploads a file, in which case you know how long the audio lasts, so you can create all the geometry when the file is uploaded. For any changes to the users settings, update the corresponding geometry attributes.

  • the user records in real time, so you don’t know how long it will take. In that case, I would have a think about how long I would expect users to record for, then create a geometry that’s large enough to hold all the points for that duration. If the user exceeds that duration, either you reset the whole thing, or you create another large geometry that can be used for a reasonable amount of time, and repeat the process each time the user goes above the limit of data that can be held in your geometry.

For animations, always use requestAnimationFrame.

For optimisation, you should avoid creating new objects in the render loop as much as possible. Try creating your vectors only once outside the render loop, and in the loop you just set them to the necessary value.

Don’t create a new material for each geometry as that’s a waste of resources. Instead, use a single LineMaterial with vertex colors enabled, and add a color attribute to your line geometry to hold each line’s colors.

Here is an example for a single geometry.
jsfiddle : Single Geometry Animating Spiral in THREE.js - JSFiddle - Code Playground

  1. I am randomizing colors but it only draws red,green and yellow, why is that ?
  2. Why is there a line from the center of the canvas to the last coordinate ? how can I remove it ?