<div class="container">
  <canvas class="illo"></canvas>
  <p><a href="https://dribbble.com/shots/4217116-Little-Forest">Original design by Alex Pasquarella</a></p>
  <p><button class="reset-button">Reset</button></p>
</div>
html { height: 100%; }

body {
  min-height: 100%;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #313;
  color: white;
  font-family: sans-serif;
  font-size: 14px;
  text-align: center;
  cursor: move;
}

canvas {
  display: block;
  margin: 0px auto 20px;
}

a { color: #936; }
a:hover { color: #D65; }

button {
  background: #525;
  color: white;
  font-size: 14px;
  font-family: inherit;
  border: none;
  border-radius: 3px;
  padding: 8px 10px;
}

button:hover {
  background: #936;
  cursor: pointer;
}
// Made with Zdog

var illoElem = document.querySelector('.illo');
var illoSize = 360;
var minWindowSize = Math.min( window.innerWidth - 20, window.innerHeight - 60 );
var zoom = Math.min( 3, Math.floor( minWindowSize / (illoSize/4) ) / 4 );
illoElem.setAttribute( 'width', illoSize * zoom );
illoElem.setAttribute( 'height', illoSize * zoom );

var isSpinning = true;
var TAU = Zdog.TAU;

var illo = new Zdog.Illustration({
  element: illoElem,
  zoom: zoom,
  dragRotate: true,
  onDragStart: function() {
    isSpinning = false;
  },
});

// colors
var midnight = '#313';
var eggplant = '#525';
var magenta = '#936';
var amber = '#D65';
var gold = '#FA6';
var white = '#FFF';

var layerSpace = 56;

// -- illustration shapes --- //

// background
var background = new Zdog.Shape({
  translate: { z: layerSpace*-2 },
  visible: false,
  addTo: illo,
});

var bgGroup1 = new Zdog.Group({
  addTo: background,
  translate: { z: -24 },
});

var bgStripe = new Zdog.Rect({
  width: 180,
  height: 44,
  addTo: bgGroup1,
  translate: { y: -40 },
  color: magenta,
  stroke: 12,
  fill: true,
});
// magenta circle
var bgCircle = new Zdog.Ellipse({
  diameter: 96,
  addTo: bgGroup1,
  translate: { y: -16 },
  color: magenta,
  stroke: 24,
  fill: true,
});

var bgGroup2 = new Zdog.Group({
  addTo: background,
});

// amber stripe
bgStripe.copy({
  addTo: bgGroup2,
  translate: { y: -8 },
  color: amber,
});
// amber circle
bgCircle.copy({
  addTo: bgGroup2,
  diameter: 64,
  translate: { y: -16, },
  color: amber,
});

var bgGroup3 = new Zdog.Group({
  addTo: background,
  translate: { z: 24 },
});

// gold bg stripe
bgStripe.copy({
  addTo: bgGroup3,
  height: 60,
  translate: { y: 32 },
  color: gold,
});
// gold circle
bgCircle.copy({
  addTo: bgGroup3,
  width: 32,
  height: 32,
  translate: { y: -16 },
  color: gold,
});

// sun
new Zdog.Shape({
  addTo: background,
  translate: { y: -16, z: 48 },
  stroke: 24,
  color: white,
});

// ----- midBackground ----- //

var midBackground = new Zdog.Group({
  addTo: illo,
  translate: { z: layerSpace*-1 },
});

var midBGDot = new Zdog.Shape({
  addTo: midBackground,
  translate: { x: -36, y: 18 },
  stroke: 24,
  color: amber,
});
midBGDot.copy({
  translate: { x: -24, y: 24 },
});
midBGDot.copy({
  translate: { x: -6, y: 26 },
});
midBGDot.copy({
  translate: { x: 12, y: 16 },
});
midBGDot.copy({
  translate: { x: 28, y: 12 },
});
midBGDot.copy({
  translate: { x: 48, y: 20 },
});

var midBGBigDot = midBGDot.copy({
  stroke: 48,
  translate: { x: -52, y: 40 },
});
midBGBigDot.copy({
  translate: { x: 20, y: 40 },
});
midBGBigDot.copy({
  stroke: 40,
  translate: { x: 56, y: 40 },
});
midBGBigDot.copy({
  stroke: 40,
  translate: { x: -16, y: 48 },
});

// ----- midground ----- //

var midground = new Zdog.Anchor({
  addTo: illo,
});

var midgroundGroundA = new Zdog.Shape({
  path: [
    { x: -96, y: 10 },
    { x: -86, y: 10 },
    { arc: [
      { x: -60, y: 42 },
      { x: -26, y: 42 },
    ]},
    { x: -26, y: 74 },
    { x: -96, y: 74 },
  ],
  addTo: midground,
  color: magenta,
  stroke: 48,
  fill: true,
});
midgroundGroundA.copy({
  path: [
    { x: -26, y: 42 },
    { arc: [
      { x: -8, y: 74 },
      { x: 36, y: 74 },
    ]},
    { x: 96, y: 74 },
    { x: -26, y: 74 },
  ],
});

function tree( groupOptions, options ) {
  options = Zdog.extend( options, groupOptions );
  var treeW = options.width/2;
  var treeH = options.height/2;

  var pointA = { x: 0, y: -treeH };
  var pointB = { x: treeW, y: treeH };
  var pointC = { x: -treeW, y: treeH };

  var treeOptions = Zdog.extend({
    path: [
      pointA,
      { bezier: [
        pointA,
        { x: treeW, y: treeH*1/3 },
        pointB,
      ]},
      pointC,
      { bezier: [
        { x: -treeW, y: treeH*1/3 },
        pointA,
        pointA,
      ]},
    ],
    fill: true,
  }, options );

  var treePlane = new Zdog.Shape( treeOptions );
  treePlane.copy({
    rotate: { y: TAU/4 },
  });

}

var midgroundTree = {
  addTo: midground,
  color: magenta,
  stroke: 2,
};

tree( midgroundTree, {
  width: 10,
  height: 24,
  translate: { x: -86, y: -14, z: -8 },
});

tree( midgroundTree, {
  width: 16,
  height: 36,
  translate: { x: -70, y: -12, z: 14 },
});

tree( midgroundTree, {
  width: 10,
  height: 24,
  translate: { x: -60, y: -4 },
});

tree( midgroundTree, {
  width: 10,
  height: 24,
  translate: { x: -26, y: 12, z: -8 },
});

tree( midgroundTree, {
  width: 10,
  height: 24,
  translate: { x: -18, y: 18, z: 2 },
});

var lonelyTranslate = { x: 32, y: 24 };

tree( midgroundTree, {
  width: 16,
  height: 36,
  translate: lonelyTranslate,
});
// lonely tree stump
new Zdog.Shape({
  path: [
    { y: 18 },
    { y: 28 },
  ],
  addTo: midground,
  translate: lonelyTranslate,
  color: magenta,
  stroke: 4,
});


tree( midgroundTree, {
  width: 10,
  height: 24,
  translate: { x: 64, y: 40, z: 6 },
});
tree( midgroundTree, {
  width: 10,
  height: 24,
  translate: { x: 72, y: 44, z: -2 },
});

// ----- midForeground ----- //

var midForeground = new Zdog.Anchor({
  addTo: illo,
  translate: { z: layerSpace },
});

// midForeground ground part A
var midForegroundGround = new Zdog.Shape({
  path: [
    { x: 96, y: 26 },
    { x: 72, y: 26 },
    { arc: [
      { x: 56, y: 50 },
      { x: 18, y: 50 },
    ]},
    { x: 18, y: 90 },
    { x: 96, y: 90 },
  ],
  addTo: midForeground,
  color: eggplant,
  stroke: 48,
  fill: true,
});
midForegroundGround.copy({
  path: [
    { x: 18, y: 50 },
    { arc: [
      { x: -16, y: 90 },
      { x: -48, y: 72 },
    ]},
    { x: -64, y: 56 },
    { x: -96, y: 48 },
    { x: -96, y: 90 },
    { x: 18, y: 90 },
  ],
});

var midForeBall = new Zdog.Shape({
  addTo: midForeground,
  translate: { x: -92, y: 18 },
  stroke: 20,
  color: eggplant,
});
midForeBall.copy({
  translate: { x: -104, y: 28 },
});
midForeBall.copy({
  translate: { x: -84, y: 28 },
  stroke: 24,
});
midForeBall.copy({
  translate: { x: -74, y: 20 },
});
midForeBall.copy({
  translate: { x: -60, y: 28 },
});
midForeBall.copy({
  translate: { x: -50, y: 36 },
});
midForeBall.copy({
  translate: { x: -44, y: 46 },
});

var midForeTree = {
  addTo: midForeground,
  color: eggplant,
  stroke: 2,
};

tree( midForeTree, {
  width: 10,
  height: 24,
  translate: { x: -12, y: 42 },
});
tree( midForeTree, {
  width: 10,
  height: 24,
  translate: { x: 10, y: 22, z: 2 },
});
tree( midForeTree, {
  width: 16,
  height: 36,
  translate: { x: 22, y: 18, z: -6 },
});

tree( midForeTree, {
  width: 16,
  height: 36,
  translate: { x: 76, y: -6, z: 12 },
});
tree( midForeTree, {
  width: 10,
  height: 24,
  translate: { x: 86, y: -4, z: -10 },
});

// ----- foregroundA ----- //

var foregroundA = new Zdog.Shape({
  path: [
    { x: -96, y: 52 },
    { x: -84, y: 52 },
    { arc: [
      { x: -72, y: 72 },
      { x: -44, y: 72 },
    ]},
    { arc: [
      { x: -32, y: 90 },
      { x: 0, y: 90 },
    ]},
    { x: -96, y: 90 },
  ],
  addTo: illo,
  translate: { z: layerSpace*2 },
  color: midnight,
  stroke: 48,
  fill: true,
});

var foregroundTree = {
  color: midnight,
  stroke: 2,
};

tree( foregroundTree, {
  addTo: foregroundA,
  width: 18,
  height: 44,
  translate: { x: -80, y: 18 },
});

var foreTree1Translate = { x: -44, y: 14 };
tree( foregroundTree, {
  addTo: foregroundA,
  width: 18,
  height: 44,
  translate: { x: -44, y: 14 },
});
// trunk
new Zdog.Shape({
  path: [
    { y: 22 },
    { y: 38 },
  ],
  addTo: foregroundA,
  translate: foreTree1Translate,
  stroke: 6,
  color: midnight,
});

tree( foregroundTree, {
  addTo: foregroundA,
  width: 16,
  height: 36,
  translate: { x: -2, y: 64 },
});

var grassBlade = new Zdog.Shape({
  path: [
    // semi-circle outside on left
    { x: 0, y: 1 },
    { arc: [
      { x: -1, y: 1 },
      { x: -1, y: 0 },
    ]},
    { arc: [
      { x: -1, y: -1 },
      { x:  0, y: -1 },
    ]},
    // shallow semi-circle back
    { arc: [
      { x: -0.5, y: -0.7 },
      { x: -0.5, y: 0 },
    ]},
    { arc: [
      { x: -0.5, y: 0.7 },
      { x: 0, y: 1 },
    ]},
  ],
  addTo: foregroundA,
  translate: { x: -20, y: 56 },
  scale: 8,
  rotate: { z: 0.6 },
  color: midnight,
  stroke: 1,
  fill: true,
  closed: false,
});
grassBlade.copy({
  translate: { x: -33, y: 50 },
  rotate: { z: TAU/2 + 0.2 },
});

grassBlade.copy({
  translate: { x: -62, y: 40 },
  rotate: { z: 0.8 },
  scale: 7,
});

grassBlade.copy({
  translate: { x: -64, y: 35 },
  rotate: { z: 0.4 },
  scale: 7,
});

// ----- foregroundB ----- //

var foregroundB = new Zdog.Shape({
  path: [
    { x: 96, y: 52 },
    { arc: [
      { x: 80, y: 72 },
      { x: 56, y: 72 },
    ]},
    { arc: [
      { x: 40, y: 90 },
      { x: 8, y: 90 },
    ]},
    { x: 0, y: 90 },
    { x: 96, y: 90 },
  ],
  addTo: illo,
  translate: { z: layerSpace*2 },
  color: midnight,
  stroke: 48,
  fill: true,
});

tree( foregroundTree, {
  addTo: foregroundB,
  width: 16,
  height: 36,
  translate: { x: 10, y: 54 },
});

// big tree
var bigTreeTranslate = { x: 58, y: 2 };
tree( foregroundTree, {
  addTo: foregroundB,
  width: 20,
  height: 64,
  translate: bigTreeTranslate,
});
// big tree trunk
new Zdog.Shape({
  path: [
    { y: 32 },
    { y: 48 },
  ],
  addTo: foregroundB,
  translate: bigTreeTranslate,
  stroke: 6,
  color: midnight,
});

tree( foregroundTree, {
  addTo: foregroundB,
  width: 16,
  height: 36,
  translate: { x: 86, y: 26 },
});

grassBlade.copy({
  addTo: foregroundB,
  scale: 12,
  translate: { x: 46, y: 54 },
  rotate: { z: 0 },
});
grassBlade.copy({
  addTo: foregroundB,
  scale: 10,
  translate: { x: 28, y: 58 },
  rotate: { z: TAU/2 - 0.4 },
});

// ----- particles ----- //

var particle = new Zdog.Shape({
  addTo: illo,
  translate: { x: -70, y: -50, z: layerSpace*-0.25 },
  stroke: 4,
  color: gold,
});
particle.copy({
  translate: { x: 68, y: -28, z: layerSpace*0.5 },
});
particle.copy({
  translate: { x: -70, y: 2, z: layerSpace*0.75 },
  color: amber,
});
particle.copy({
  translate: { x: 74, y: 14, z: layerSpace*1.5 },
});
particle.copy({
  translate: { x: -24, y: 34, z: layerSpace*1.75 },
});
particle.copy({
  translate: { x: 34, y: 34, z: layerSpace*1.9 },
  color: amber,
});
particle.copy({
  translate: { x: 22, y: 40, z: layerSpace*2.2 },
});

// ----- clouds ----- //

var twoCloud = new Zdog.Shape({
  path: [
    { x: -20, y: 0 },
    { bezier: [
      { x: -13, y: 0 },
      { x: -12, y: -4 },
      { x: -10, y: -4 },
    ]},
    { bezier: [
      { x: -8, y: -4 },
      { x: -8, y: -2 },
      { x: -4, y: -2 },
    ]},
    { bezier: [
      { x: 0, y: -2 },
      { x: 1, y: -6 },
      { x: 4, y: -6 },
    ]},
    { bezier: [
      { x: 7, y: -6 },
      { x: 6, y: 0 },
      { x: 20, y: 0 },
    ]},
  ],
  addTo: illo,
  translate: { x: -84, y: -38, z: layerSpace*-1 },
  rotate: { y: -TAU*1/16 },
  scale: { x: 1/Math.cos(TAU*1/16) },
  stroke: 4,
  color: white,
  fill: true,
});

twoCloud.copy({
  translate: { x: -38, y: -22, z: layerSpace*-0.5 },
  rotate: { y: -TAU*1/8 },
  scale: { x: 1/Math.cos(TAU*1/8) * -1 },
});

// triple cloud
new Zdog.Shape({
  path: [
    { x: -32, y: 0 },
    { x: -28, y: 0 },
    { bezier: [
      { x: -22, y: 0 },
      { x: -20, y: -6 },
      { x: -16, y: -6 },
    ]},
    { bezier: [
      { x: -12, y: -6 },
      { x: -12, y: -2 },
      { x: -8, y: -2 },
    ]},
    { bezier: [
      { x: -4, y: -2 },
      { x: -4, y: -6 },
      { x: 0, y: -6 },
    ]},
    { bezier: [
      { x: 4, y: -6 },
      { x: 4, y: -2 },
      { x: 8, y: -2 },
    ]},
    { bezier: [
      { x: 12, y: -2 },
      { x: 12, y: -6 },
      { x: 16, y: -6 },
    ]},
    { bezier: [
      { x: 20, y: -6 },
      { x: 22, y: 0 },
      { x: 28, y: 0 },
    ]},
    { x: 32, y: 0 },
  ],
  addTo: illo,
  translate: { x: 72, y: -52, z: layerSpace*-1 },
  rotate: { y: TAU * 1/16 },
  scale: { x: 1/Math.cos(TAU * -1/16) },
  stroke: 4,
  color: white,
  fill: true,
});

// ----- stars ----- //

var starA = new Zdog.Shape({
  path: [
    { x: 0, y: -4 },
    { arc: [
      { x: 0, y: 0 },
      { x: 4, y: 0 },
    ]},
    { arc: [
      { x: 0, y: 0 },
      { x: 0, y: 4 },
    ]},
    { arc: [
      { x: 0, y: 0 },
      { x: -4, y: 0 },
    ]},
    { arc: [
      { x: 0, y: 0 },
      { x: 0, y: -4 },
    ]},
  ],
  addTo: illo,
  translate: { x: -50, y: -50, z: layerSpace*-1.5 },
  color: gold,
  stroke: 2,
  fill: true,
});
starA.copy({
  rotate: { y: TAU/4 },
});

var starB = starA.copy({
  translate: { x: 42, y: -20, z: -layerSpace*0.5 },
});
starB.copy({
  rotate: { y: TAU/4 },
});

// ----- bird ----- //

new Zdog.Shape({
  path: [
    { x: -6, y: -4 },
    { x: -4, y: -4 },
    { arc: [
      { x: 0, y: -4 },
      { x: 0, y: 0 },
    ]},
    { arc: [
      { x: 0, y: -4 },
      { x: 4, y: -4 },
    ]},
    { x: 6, y: -4 },
    { move: { z: -2, y: 0 } },
    { z: 3, y: 0 },
  ],
  addTo: illo,
  translate: { x: 18, y: -30, z: layerSpace*-1 },
  stroke: 3,
  color: midnight,
  closed: false,
});


// -- animate --- //

var t = 0;
var tSpeed = 1/240;

function animate() {
  // update
  if ( isSpinning ) {
    t += tSpeed;
    var theta = Zdog.easeInOut( t % 1 ) * TAU;
    var delta = TAU * -3/64;
    illo.rotate.y = Math.sin( theta ) * delta;
    illo.rotate.x = ( Math.cos( theta ) * -0.5 + 0.5 ) * delta;
  }

  illo.updateRenderGraph();
  requestAnimationFrame( animate );
}

animate();


// ----- inputs ----- //

document.querySelector('.reset-button').onclick = function() {
  isSpinning = false;
  illo.rotate.set({});
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/zdog@1/dist/zdog.dist.js