please load my fonts
html, body, canvas
  font-family: "Playfair Display SC", sans-serif
  font-weight: 900
  font-size: 0
  width: 100%
  height: 100%
  overflow: hidden
  padding: 0
  margin: 0
  left: 0
  top: 0
View Compiled
var _seed = new Date().getTime();
var __seed = _seed;

var $canvas;
var _width, _height;
var _color, _tree;
var _c, _scale = 1;

var _message = 0;

const _messages = [
  "Merry Christmas",
  "عيد ميلاد%مجيد",
  "圣诞快乐",
  "Čestit Božić",
  "Veselé Vánoce",
  "Glædelig Jul",
  "Prettige Kerstdagen",
  "Häid jõule",
  "Hauskaa joulua",
  "Joyeux Noël",
  "Frohe Weihnachten",
  "Χαρούμενα Χριστούγεννα",
  "Buon Natale",
  "メリー クリスマス",
  "메리 크리스마스",
  "Priecīgus Ziemassvētkus",
  "Linksmų%šv. Kalėdų",
  "Среќен Божиќ",
  "God%jul",
  "Wesołych Świąt!",
  "Feliz Natal",
  "Crăciun fericit",
  "Счастливого Рождества!",
  "Srećan Božić",
  "Kirismas Wanaagsan",
  "Feliz Navidad",
  "Heri%ya krismas",
  "God%jul",
  "Веселого Різдва",
  "Bon Nadal"
];

// utils

function random() {
  let x = Math.sin(__seed++) * 10000;
  return x - Math.floor(x);
}

function range( v = 0, omin = 0, omax = 1, nmin = 0, nmax = 1, clamp = false ) {
  let result = ( v - omin ) * ( nmax - nmin) / ( omax - omin ) + nmin;
  if ( clamp ) result = Math.min( Math.max( result, nmin ), nmax );
  return result;
}

function radians( degrees = 0 ) {
  return degrees * (Math.PI / 180);
}

function angleTo( p1, p2 ) {
  return Math.atan2(p2.y - p1.y, p2.x - p1.x);
}

function distance( p1, p2 ) {
  return Math.sqrt( Math.pow( p1.x - p2.x, 2 ) + Math.pow( p1.y - p2.y, 2 ));
}

function vectorOnCurve( time, p1, p2, p3 ) {
  let x = (1 - time) * ((1 - time) * p1.x + (time * p2.x)) + time * (((1 - time) * p2.x + (time * p3.x)));
  let y = (1 - time) * ((1 - time) * p1.y + (time * p2.y)) + time * (((1 - time) * p2.y + (time * p3.y)));
  return { x , y };
}

// constructor

( _ => {
  initCanvas();
  addHandlers();
  resize();
  
  setInterval( _ => {
    _seed = new Date().getTime();
    resize();
  }, 1000 );
  
})();

// setup

function initCanvas() {
  $canvas = document.createElement('canvas');
  document.body.appendChild( $canvas );
  _c = $canvas.getContext('2d');
}

function addHandlers() {
  window.addEventListener( 'resize', resize );
}

function resize() {
  let ratio = window.devicePixelRatio || 1;
  __seed = _seed;
  _width = window.innerWidth * ratio;
  _height = window.innerHeight * ratio;
  $canvas.setAttribute( 'width', _width );
  $canvas.setAttribute( 'height', _height );
  $canvas.style.width = `${_width / ratio }px`;
  $canvas.style.height = `${_height / ratio }px`;
  render();
}

// draw call

function render() {
  setColors();
  generateTree();
  drawBackground();
  drawBaubels();
  drawTrunk();
  drawPresents();
  drawNeedles( 2.0 );
  drawGarland( 10 );
  drawNeedles( 0.75 );
  drawGarland( -3 );
  drawBaubels();
  drawNeedles( 0.125 );
  drawStar();
  drawMessage();
}

function setColors() {
  _color = {};
  
  let wh = range( random(), 0, 1, 13, 60 );
  let ws = range( random(), 0, 1, 5, 25 );
  let wl = range( random(), 0, 1, 25, 32 );
  _color.branch = `hsla(${wh},${ws}%,${wl}%,1)`;
  
  let nh = range( random(), 0, 1, 90, 175 );
  let ns = range( random(), 0, 1, 15, 45 );
  let nl = range( random(), 0, 1, 25, 42 );
  _color.needle = `hsla(${nh},${ns}%,${nl}%,0.6)`;
  
  let bh = range( random(), 0, 1, -90, 90 );
  let bs = range( random(), 0, 1, 45, 55 );
  let bl = range( random(), 0, 1, 50, 65 );
  
  _color.bauble = `hsla(${bh},${bs}%,${bl}%,1.0)`;
  _color.garland = `hsla(${bh + 120},${bs}%,${bl + 15}%,0.75)`;
  _color.presents = `hsla(${bh - 120},${bs}%,${bl - 25}%,1.0)`;
  _color.star = `hsla(${bh + 120},${bs}%,90%,1.0)`;
  _color.background = `hsla(${nh},${ns * 0.5}%,85%,1.0)`
}

function generateTree() {
  _tree = {};
  generateTrunk();
  generateBranches();
}

function generateTrunk() {
  _tree.trunk = [];
  _tree.thickness = range( random(), 0, 1, 15, 21 );
  
  let steps = Math.floor( range( random(), 0, 1, 7, 16 ));
  let step = 0;
  
  let x = _width * 0.5;
  let y = 0;
  
  _tree.trunk.push({ x, y });
  while ( step < steps ) {
    let angle = radians( range( random(), 0, 1, 75, 105 ));
    let mult = range( steps - step, 0, steps, 35, 60 );
    let dist = range( random(), 0, 1, mult * 0.666, mult * 1.333 ) * _scale;
    x += Math.round( Math.cos( angle ) * dist );
    y -= Math.floor( Math.sin( angle ) * dist ) + 1;
    _tree.trunk.push({ x, y });
    step++;
  }
  
  let offset = Math.floor(( _height + Math.abs( y )) * 0.5 );
  for ( let step of _tree.trunk ) {
    step.y += offset;
  }
  
}

function generateBranches() {
  _tree.branches = [];
  
  let index = 0;
  for ( let step of _tree.trunk ) {
    let mult = Math.abs( range( index - _tree.trunk.length, 0, _tree.trunk.length, 0.1, 1 )) * _scale;
    let total = Math.round( range( random(), 0, 1, 4, range( mult, 0, 1, 4, 12 )));
    if ( index <= 1 ) total = 0;
    
    let i = 0;
    while ( i < total ) {
      let ox = step.x;
      let oy = step.y;
      let angle = range( mult, 0, 1, 45, 5 );
      let dist = range( random(), 0, 1, 100, 200 ) * range( mult, 0, 1, 0.35, 1.5 ) * _scale;
      angle += range( random(), 0, 1, -15, 15 );
      angle = radians( angle );
      
      let dx = ox + ( Math.cos( angle ) * dist ) * ( i / 2 === Math.round( i / 2) ? 1 : -1 );
      let dy = oy + Math.sin( angle ) * dist;
      
      _tree.branches.push({
        p1: { x: ox, y: oy },
        p2: { x: ox + ( dx - ox ) * 0.5, y: dy + ( dy - oy ) * 0.35 },
        p3: { x: dx, y: dy },
        thickness: _tree.thickness * ( 1 - (( index + 1 ) / _tree.trunk.length ))
      });
      
      i++;
    }
    
    index++;
  }
  
}

function drawBackground() {
  let fill = _color.background;
  _c.fillStyle = fill;
  _c.fillRect( 0, 0, _width, _height );
}

function drawTrunk() {
  let index = 0;
  _c.fillStyle = _color.branch;
  _c.strokeStyle = '';
  
  for ( let segment of _tree.trunk ) {
    let next = _tree.trunk[ index + 1 ];
    if ( !next ) break;
    
    let tThick = _tree.thickness * ( 1 - (( index + 1 ) / _tree.trunk.length ));
    let bThick = _tree.thickness * ( 1 - ( index / _tree.trunk.length ));
    
    _c.beginPath();
    _c.moveTo( segment.x - bThick, segment.y );
    _c.lineTo( next.x - tThick, next.y );
    _c.lineTo( next.x + tThick, next.y );
    _c.lineTo( segment.x + bThick, segment.y );
    _c.closePath();
    _c.fill();
    index++;
  }
}

function drawBranches() {
  let index = 0;
  _c.fillStyle = '';
  _c.strokeStyle = _color.branch;
  
  for ( let branch of _tree.branches ) {
    _c.beginPath();
    _c.moveTo( branch.p1.x, branch.p1.y );
    _c.quadraticCurveTo( branch.p2.x, branch.p2.y, branch.p3.x, branch.p3.y );
    _c.lineWidth = Math.max( branch.thickness * 0.8, 3 ) * _scale;
    _c.stroke();
  }
}

function drawNeedles( mult = 1 ) {
  _c.fillStyle = '';
  _c.strokeStyle = _color.needle;
  
  for ( let branch of _tree.branches ) {
    let total = distance( branch.p1, branch.p3 ) * mult;
    let i = 0;
    while ( i < total ) {
      let origin = vectorOnCurve( i / total, branch.p1, branch.p2, branch.p3 );
      let angle = radians( range( random(), 0, 1, 0, 360 ));
      let length = range( random(), 0, 1, 6, 18 ) * _scale;
      
      origin.x += Math.cos( angle ) * ( 5 + branch.thickness ) * _scale;
      origin.y += Math.sin( angle ) * ( 5 + branch.thickness ) * _scale;
      
      let x = origin.x + Math.cos( angle ) * length;
      let y = origin.y + Math.sin( angle ) * length;
      let thickness = range( random(), 0, 1, 1.5, 2 ) * _scale;
      
      _c.beginPath();
      _c.lineWidth = thickness;
      _c.moveTo( origin.x, origin.y );
      _c.lineTo( x, y );
      _c.stroke();
      
      i++;
    }
  }
}

function drawBaubels() {
  let total = _tree.branches.length;
  let index = 0;
  
  _c.strokeStyle = '';
  
  while ( index < total ) {
    
    if ( random() > 0.75 ) {
      let branch = _tree.branches[ index ];
      let point = vectorOnCurve( random(), branch.p1, branch.p2, branch.p3 );
      let radius = range( random(), 0, 1, 8, 10 ) * _scale;

      
      _c.fillStyle = '';
      _c.strokeStyle = _color.background;
      _c.lineWidth = 1;
      
      _c.beginPath();
      _c.moveTo( point.x, point.y + 10 );
      _c.lineTo( point.x, point.y - 5 );
      _c.stroke();
      
      _c.fillStyle = _color.bauble;

      _c.beginPath();
      _c.arc( point.x, point.y + 10, radius, 0, Math.PI * 2 );
      _c.fill();
    }
    
    index++;
  }
}

function drawGarland( mult = 1 ) {
  let index = Math.floor( range( random(), 0, 1, 5, 12 )) + mult;
  let past = _tree.branches[index];
  
  _c.fillStyle = '';
  _c.strokeStyle = _color.garland;
  _c.lineWidth = 4 * _scale;
  _c.lineCap = 'round';
  
  while ( index < _tree.branches.length ) {
    let current = _tree.branches[ index ];
    let p1 = vectorOnCurve( 1, past.p1, past.p2, past.p3 );
    let p3 = vectorOnCurve( 1, current.p1, current.p2, current.p3 );
    
    if ( 
      distance( p1, p3 ) > 100 &&
      (( p1.x < _width / 2 && p3.x > _width / 2 ) ||
      ( p1.x > _width / 2 && p3.x < _width / 2 ))
    ) {  
      let p2 = {
        x: ( p1.x + p3.x ) / 2,
        y: Math.max( p1.y, p3.y )
      }

      _c.beginPath();
      _c.moveTo( p1.x, p1.y );
      _c.quadraticCurveTo( p2.x, p2.y, p3.x, p3.y );
      _c.stroke();
      
      past = current;
      index += Math.floor( _tree.branches.length * 0.25 );
    } else {
      index++;
    }
  }
  
  _c.lineCap = 'butt';
}

function drawPresents() {
  let i = 0;
  let ox = _width * 0.5;
  let oy = _tree.trunk[0].y;
  let offset = 0;
  
  _c.fillStyle = _color.presents;
  _c.strokeStyle = '';
  
  
  let bs = range( random(), 0, 1, 35, 45 );
  let bh = range( random(), 0, 1, -120, 120 );
  
  while ( i < range( random(), 0, 1, 10, 16 )) {
    let w = Math.floor( range( random(), 0, 1, 30, 110 )) * _scale;
    let h = Math.floor( range( random(), 0, 1, 30, 110 )) * _scale;
    let x = Math.floor( ox + range( random(), 0, 1, -140, 140 ) - w * 0.5 ) * _scale;
    let y = Math.floor( oy - h );
    y += offset;
    
    let bl = range( random(), 0, 1, 35, 85 );
    _color.presents = `hsla(${bh - 120 },${bs}%,${bl - 15}%,1.0)`;
    
    _c.beginPath();
    _c.fillStyle = _color.presents;
    _c.fillRect( x, y, w, h );
    
    let ribbon = 5;
    _c.fillStyle = _color.garland;
    
    _c.fillRect( x, y + ( h - ribbon ) * 0.5, w, ribbon );
    _c.fillRect( x + ( w - ribbon ) * 0.5, y, ribbon, h );
    
    offset += range( random(), 0, 1, 3, 8 );
    
    i++;
  }
}

function drawStar() {
  let points = Math.floor( range( random(), 0, 1, 5, 12 ));
  let point = _tree.trunk[_tree.trunk.length - 1];
  let inner = range( random(), 0, 1, 15, 20 ) * _scale;
  let outer = inner + range( random(), 0, 1, 15, 25 ) * _scale;
  let step = 360 / points;
  
  _c.fillStyle = _color.star;
  _c.strokeStyle = _color.star;
  _c.lineWidth = 5;
  _c.lineCap = 'round';
  
  let angle = -90;
  while ( angle < 270 ) {
    
    let x = point.x + Math.cos( radians( angle )) * outer;
    let y = point.y + Math.sin( radians( angle )) * outer;
    
    let x2 = point.x + Math.cos( radians( angle + step * 0.5 )) * inner;
    let y2 = point.y + Math.sin( radians( angle + step * 0.5 )) * inner;
    
    let x3 = point.x + Math.cos( radians( angle - step * 0.5 )) * inner;
    let y3 = point.y + Math.sin( radians( angle - step * 0.5 )) * inner;
    
    _c.beginPath();
    _c.moveTo( x, y );
    _c.lineTo( x2, y2 );
    _c.lineTo( point.x, point.y );
    _c.lineTo( x3, y3 );
    _c.lineTo( x, y );
    //_c.fill();
    
    _c.beginPath();
    _c.moveTo( x, y );
    _c.lineTo( x2, y2 );
    _c.stroke();
    
    _c.beginPath();
    _c.moveTo( x, y );
    _c.lineTo( x3, y3 );
    _c.stroke();
    
    angle += step;
  }
  
  // _c.beginPath();
  // _c.moveTo( point.x, point.y );
  // _c.arc( point.x, point.y, inner, 0, Math.PI * 2 );
  // _c.fill();
  
  _c.lineCap = 'butt';
}

function drawMessage() {
  let size = range( _width, 600 * 2, 1400 * 2, 95, 120 );
  
  _c.globalCompositeOperation = 'multiply';
  _c.fillStyle = 'hsl(12, 85%, 56%)';
  _c.textAlign = 'center';
  _c.font = `normal 900 ${size}px "Playfair Display SC", Serif`;
  
  let string = _messages[ _message ];
   _message++;
  if ( _message > _messages.length - 1 ) _message = 0;
  
  
  let words = string.split(' ');
  
  let spacing = size * 0.85;
  let offset = words.length * ( 0.5 * spacing ) - size;
  
  let x = _width * 0.5;
  let y = _height * 0.5 - offset;
  
  let i = 0;
  for ( let word of words ) {
    word = word.replace(/%/g, " ");
    _c.fillText( word, x, y + i * spacing );
    i++;
  }
  
  _c.globalCompositeOperation = 'source-over';
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.