Animated Fractal Tree

Draw a Fractal Tree with nothing but React.

This series of examples contains the full code for the tree in the guide Learn React By Itself.

The helper getBoxStyle() has also been included directly in the example, so that you can mess around with the math – if you’re brave enough.

// Each TreeBox element will render a colored div, and two more
// TreeBox elements that represent its leaves.
const TreeBox = (props) => {
  const style = getBoxStyle(props)
  const baseProps = Object.assign({}, props, {
    level: props.level + 1,
  })
  const leftChild =
    props.level < props.totalLevels &&
    React.createElement(TreeBox, Object.assign({}, baseProps, { right: false }))
  const rightChild =
    props.level < props.totalLevels &&
    React.createElement(TreeBox, Object.assign({}, baseProps, { right: true }))

  return React.createElement('div', { style },
    leftChild,
    rightChild,
  )
}

// Render the root TreeBox element.
ReactDOM.render(
  React.createElement(TreeBox, {
    heightFactor: 0.37,
    lean: 0.05,
    size: 100,
    totalLevels: 5,
    level: 0,
  }),
  document.getElementById('app')
)


//
// Helpers
//

function getBoxStyle({ size, heightFactor, lean, level, totalLevels, right }) {
  const color = interpolateColor((level/totalLevels)**2, 80, 120, 54, 240, 104, 64)
  const scale = right
      ? Math.sqrt((size*heightFactor)**2 + (size * (0.5+lean))**2) / size
      : Math.sqrt((size*heightFactor)**2 + (size * (0.5-lean))**2) / size
  const rotation =
    right
      ? Math.atan(heightFactor / (0.5+lean))
      : -Math.atan(heightFactor / (0.5-lean))

  const style = {
    position: 'absolute',
    bottom: 0,
    width: size,
    height: size,
    transformOrigin: right ? `${size}px ${size}px` : `0 ${size}px`,
    transform: level === 0 ? '' : `
      translate3d(0, ${-size}px, 0)
      scale3d(${scale}, ${scale}, 1)
      rotate(${rotation}rad)
    `,
    backgroundColor: color,
  }

  if (level === 0) {
    style.left = `calc(50% - ${size/2}px)`
  }

  return style
}


function interpolateColor(x, r1, r2, g1, g2, b1, b2) {
  const r = Math.round(clamp(x, r1, r2))
  const g = Math.round(clamp(x, g1, g2))
  const b = Math.round(clamp(x, b1, b2))
  return `rgb(${r}, ${g}, ${b})`
}


function clamp(x, min, max) {
  return min + (max - min)*x
}