-1

HTML Canvas or Three.js experts,

I have a task this is very similar to 1 and I have big difficulties in finding a solution, most probably because I am a newbie in THREE.js/HTML Canvas, a suggestion on how to approach this would be very helpful.

Context: this is an animation on a presentation website, when the animation starts the layers pivot up and down and rotate at the end, when the animation finishes, users can hover on top of the shape and the zone hovered on the first layer (the one with dots) pushes the dots away on the axis.

My questions:

  1. How to draw an equilateral triangle with rounded corners and add that glow effect? (I searched the documentation and multiply answers but could not find a solution)
  2. I managed to build the functionality of the first layer as a square of dots and integrated the hover effect, but I could not find a solution to make the first layer crop/clip-path/reduce the opacity of the dots that are not included in the shadow of the top layer. So my question is, how to make the bottom layer dots style depend on the clone of the top layer?
  3. Is this the right approach? I planned this: Create the top layer triangle, create the bottom layer as a square of dots, clone the top layer triangle on top of the bottom square of dots and move its position a little to represent a shadow, then loop through the dots and check if they are inside the clone of the top layer triangle (here I can play with opacities and clip paths/crops on the dots inside/outside/close to the clone of the triangle).
  4. Is three.js too much for what I need, should I try to use a regular HTML Canvas?

Thank you for your time and kindness! A code sample that shows how to start or a proof of concept would be very helpful.

Willie Cheng
  • 7,679
  • 13
  • 55
  • 68

1 Answers1

1

I'd say using three.js for this task is a bit too much. A plain HTML <canvas> element in conjunction with it's drawing methods will do it just right.

Nevertheless - out of the box the CanvasRenderingContext2d does not have a built-in method to round corners. This is something that has to be done using a bit of trigonometry.

To get a better picture let's say we have a triangle with the points:

A=(50,50) ; B=(250,100) ; C=(50,350)

This is not an equilateral triangle but the concept applies to any triangle of course. On screen this will resemble a shape like this:

If we think about it, to have a rounded corner at point B, there must be a curve going somewhere from side c, through point B to side a. But where?

Let's overlay a green circle of radius 20 at point B.

We can see that the intersection of the circle with side c and side a should be the start and respectively the end point of the curve. As the radius is 20, we need to travel 20 pixels from point B in the direction of point A and likewise 20 pixels from point B in direction of point C. The direction is given by the angle in between two particular points and can be obtained using the Math.atan2(pointB.x-pointA.x, pointB.y-pointA.y) method.

If we calculate those two points - and repeat the process for the remaining two vertices of the triangle - and draw the curves using the bezierCurveTo() method we finally have rounded corners.

That's it for the first part of your question.

To give the outlines of the triangle a glowing look, we can use the built-in filter effects. A blur() filter mimics a glow just fine. The repeating pattern of the triangle in the background isn't that hard to do either. A closed shape on the canvas can be filled by a custom pattern, created using the createPattern() method. All we need to do is draw a seamless tile with a black circle in the middle. To do this we can create a temporary <canvas> element of equal width and height using document.createElement("canvas") and draw a circle using the arc() method.

Here is the complete example:

let triangle = {
  a: {
    x: 50,
    y: 50
  },
  b: {
    x: 250,
    y: 100
  },
  c: {
    x: 50,
    y: 350
  }
};

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let cornerRadius = 60;

let corners = [
  [triangle.a, triangle.b, triangle.c],
  [triangle.b, triangle.c, triangle.a],
  [triangle.c, triangle.a, triangle.b]
]

function drawTriangle(offsetX = 0, offsetY = 0) {
  ctx.beginPath();
  let angle, corner, pA, pB;
  let segments = [];

  for (let a = 0; a < corners.length; a++) {
    corner = corners[a];
    angle = Math.atan2(corner[1].x - corner[0].x, corner[1].y - corner[0].y) + Math.PI;
    pA = {
      x: corner[1].x + offsetX + Math.sin(angle) * cornerRadius,
      y: corner[1].y + offsetY + Math.cos(angle) * cornerRadius
    }

    angle = Math.atan2(corner[1].x - corner[2].x, corner[1].y - corner[2].y) + Math.PI;

    pB = {
      x: corner[1].x + offsetX + Math.sin(angle) * cornerRadius,
      y: corner[1].y + offsetY + Math.cos(angle) * cornerRadius
    };
    segments.push(pA)
    segments.push({
      x: corner[1].x + offsetX,
      y: corner[1].y + offsetY
    });
    segments.push(pB);
  }

  let temp;
  ctx.moveTo(segments[0].x, segments[0].y);
  for (let a = 0; a < segments.length; a += 3) {
    temp = a + 3 == segments.length ? 0 : a + 3;
    ctx.bezierCurveTo(segments[a + 1].x, segments[a + 1].y, segments[a + 1].x, segments[a + 1].y, segments[a + 2].x, segments[a + 2].y);
    ctx.lineTo(segments[temp].x, segments[temp].y);
  }

  ctx.closePath();
}

function createPattern() {
  let tempCanvas = document.createElement("canvas");
  let tempContext = tempCanvas.getContext("2d");
  let radius = 3;
  tempCanvas.width = 12;
  tempCanvas.height = 12;
  tempContext.beginPath();
  tempContext.arc(tempCanvas.width / 2, tempCanvas.height / 2, radius, 0, 2 * Math.PI);
  tempContext.fill();
  tempContext.closePath();

  return ctx.createPattern(tempCanvas, 'repeat');
}

let rad = 0;
let radius = 25;
let fillPattern = createPattern();

function animate() {
  ctx.fillStyle = "#ecebcc";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = fillPattern;
  ctx.save();
  ctx.translate(Math.sin(rad) * radius, Math.cos(rad) * radius);
  drawTriangle();
  ctx.fill();
  ctx.restore();
  ctx.lineWidth = 7;
  ctx.strokeStyle = "#378f43"
  ctx.filter = "blur(10px)";
  drawTriangle();
  ctx.stroke();
  ctx.filter = "none";
  ctx.lineWidth = 1;
  drawTriangle();
  ctx.stroke();
  rad += Math.PI / 180;
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);
<canvas id="canvas" width="400" height="400"></canvas>
obscure
  • 11,916
  • 2
  • 17
  • 36