<svg>
  <!--  https://tympanus.net/codrops/2019/02/19/svg-filter-effects-creating-texture-with-feturbulence/  -->
  <filter id='paper' x="0" y="0" width='100%' height="100%">

    <feTurbulence type="fractalNoise" baseFrequency='0.05' result='noise' numOctaves="6" />

    <feDiffuseLighting in='noise' lighting-color='white' surfaceScale='3'>
      <feDistantLight azimuth='45' elevation='60' />
    </feDiffuseLighting>

  </filter>
</svg>

<div class="card">

  <svg viewBox="0 0 200 200" class="stipple">

  </svg>
</div>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  width: 100vw;
  height: 100vh;
  background: #f4f4f4;
}

.card {
  position: absolute;
  width: 85vmin;
  height: 85vmin;
  padding: 2.5vmin;
  grid-gap: 1vmin;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
  background: #fff;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: 1px solid #f1f1f1;
}

.card > div {
  position: relative;
  z-index: 1;
  overflow: hidden;
  transform: transalte3d(0px, 0px, 0px);
}

.card > div svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.stipple {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 1;
  height: 100%;
}
const fillColor = "#111";

// Utils
function random(min, max) {
  return Math.random() * (max - min) + min;
}

function map(n, start1, end1, start2, end2) {
  return ((n - start1) / (end1 - start1)) * (end2 - start2) + start2;
}

function percentage(partialValue, totalValue) {
  return (100 * partialValue) / totalValue;
}

function lerp(x1, y1, x2, y2, amt) {
  const x = x1 + (x2 - x1) * amt;
  const y = y1 + (y2 - y1) * amt;

  return { x, y };
}

// Tree
class Tree {
  constructor(x, y, SVGElement) {
    this.x = x;
    this.y = y;
    this.initY = y;

    this.canvas = SVG(SVGElement);

    this.runDebug = false;
  }

  initialiseVars() {
    this.width = random(60, 120);
    this.height = random(80, 180);
    this.y = this.initY - (200 - this.height) / 2;
    this.trunkWidth = random(0.5, 1);
    this.branchVerticalLevels = ~~random(6, 24);

    const rotation = random(5, 10);

    if (random(0, 1) > 0.5) {
      this.branchUpwardsRotation = 100 - rotation;
    } else {
      this.branchUpwardsRotation = 100 + rotation;
    }
  }

  render() {
    this.clear();

    this.initialiseVars();

    if (this.runDebug) {
      this.debug();
    }

    this.renderBranches();
    this.renderTrunk();
  }

  renderTrunk() {
    const numTrunks = ~~random(1, 5);

    for (let i = 0; i < numTrunks; i++) {
      const trunk = this.canvas
        .rect(this.trunkWidth * random(0.5, 1), this.height)
        .cx(this.x)
        .y(this.y - this.height)
        .rotate(random(-0.5, 0.5))
        .fill(fillColor);
    }
  }

  renderBranches() {
    const bottomPadding = random(10, 30);
    const branchVertInc =
      (this.height - bottomPadding) / this.branchVerticalLevels;

    const maxBranchWidth = this.width / random(2, 3);
    const minBranchWidth = this.width / random(12, 24);

    this.maxBranchWidth = maxBranchWidth;
    this.minBranchWidth = minBranchWidth;

    for (let i = 0; i < this.branchVerticalLevels; i++) {
      const branchWidth = map(
        i,
        0,
        this.branchVerticalLevels - 1,
        maxBranchWidth,
        minBranchWidth
      );

      const yPos = this.y - bottomPadding - branchVertInc * i;

      // left
      this.branch(this.x, yPos, this.x - branchWidth, yPos, i, "left");

      // right
      this.branch(this.x, yPos, this.x + branchWidth, yPos, i, "right");
    }
  }

  branch(x1, y1, x2, y2, level, side) {
    const branch = this.canvas.group();

    y2 = percentage(y2, this.branchUpwardsRotation);

    const numSubBranches = ~~random(10, 30);

    for (let i = 0; i < numSubBranches; i++) {
      const startPos = random(0, 0.75);
      const len = random(0.125, 0.375);
      const start = lerp(x1, y1, x2, y2, startPos);
      const end = lerp(x1, y1, x2, y2, startPos + len);

      const size = Math.hypot(x2 - x1, y2 - y1);
      const maxRotation = map(
        size,
        this.minBranchWidth,
        this.maxBranchWidth,
        2,
        8
      );

      const startX = start.x;
      const startY = start.y;
      const endX = end.x;
      const endY = end.y + random(-maxRotation, maxRotation);

      const line = branch.line(startX, startY, endX, endY).stroke({
        width: this.trunkWidth * random(0.25, 1),
        color: fillColor
      });

      if (random(0, 1) > 0.99) {
        this.canvas
          .ellipse(random(1, 2), random(1, 2))
          .cx(endX)
          .cy(endY)
          .fill("#ef233c")
          .opacity(random(0, 1));
      } else {
        if (random(0, 1) > 0.99) {
          this.canvas
            .circle(random(1, 2), random(1, 2))
            .cx(startX)
            .cy(startY)
            .fill("#ef233c")
            .opacity(random(0, 1));
        }
      }
    }
  }

  clear() {
    // this.canvas.clear();
  }

  debug() {
    this.canvas
      .rect(this.width, this.height)
      .cx(this.x)
      .y(this.y - this.height)
      .fill("rgba(255, 0, 0, 0.125)");
  }
}

setInterval(() => {
  render();
}, 2000);

function render() {
  const trees = [];

  const cellSize = ~~random(1, 4);
  const numTrees = cellSize * cellSize;
  const card = document.querySelector(".card");

  card.innerHTML = '<svg viewBox="0 0 200 200" class="stipple"></svg>';

  card.style.gridTemplateColumns = `repeat(${cellSize}, 1fr)`;
  card.style.gridTemplateRows = `repeat(${cellSize}, 1fr)`;

  const stippleCanvas = SVG(".stipple");

  stipple();

  for (let i = 0; i < numTrees; i++) {
    const el = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    el.setAttribute("viewBox", "0 0 200 200");

    const wrapper = document.createElement("div");

    wrapper.appendChild(el);
    card.appendChild(wrapper);

    const tree = new Tree(100, 200, el);
    trees.push(tree);

    tree.render();
  }

  function stipple() {
    const r = stippleCanvas.rect(200, 200).fill("#fff");
    r.node.setAttribute("filter", "url(#paper)");
    r.opacity(random(0.4, 0.625));

    stippleCanvas.node.style.opacity = random(0.3, 0.6);

    for (let i = 0; i < 150; i++) {
      const snowFlake = stippleCanvas
        .ellipse(random(0.1, 0.5), random(0.1, 0.5))
        .cx(random(0, 200))
        .cy(random(0, 200))
        .fill(fillColor);
    }
  }
}

render();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js
  2. https://unpkg.co/gsap@3/dist/gsap.min.js