var Lightbulbs = {
  bulbVertices: [
  {"x":22.903060913085938,"y":1.7348792552947998},{"x":22.08816909790039,"y":4.591442584991455},{"x":21.692092895507812,"y":5.976990699768066},{"x":20.907676696777344,"y":8.715706825256348},{"x":20.133705139160156,"y":11.411376953125},{"x":19.370330810546875,"y":14.064120292663574},{"x":18.992616653442383,"y":15.374613761901855},{"x":18.24593162536621,"y":17.961475372314453},{"x":17.438720703125,"y":20.752822875976562},{"x":16.637187957763672,"y":23.519771575927734},{"x":15.85030460357666,"y":26.232173919677734},{"x":15.462624549865723,"y":27.567285537719727},{"x":14.698760986328125,"y":30.195972442626953},{"x":13.950777053833008,"y":32.76813888549805},{"x":13.135793685913086,"y":35.569549560546875},{"x":12.330108642578125,"y":38.33896255493164},{"x":11.935434341430664,"y":39.695980072021484},{"x":11.162497520446777,"y":42.35520553588867},{"x":10.41190242767334,"y":44.94084930419922},{"x":9.628293991088867,"y":47.64549255371094},{"x":8.817490577697754,"y":50.45214080810547},{"x":8.424226760864258,"y":51.817344665527344},{"x":7.662332534790039,"y":54.47154998779297},{"x":6.9340057373046875,"y":57.02335739135742},{"x":6.136892318725586,"y":59.837764739990234},{"x":5.359792709350586,"y":62.61057662963867},{"x":4.990869522094727,"y":63.939876556396484},{"x":4.292514801025391,"y":66.48577880859375},{"x":3.529491424560547,"y":69.32653045654297},{"x":2.8134613037109375,"y":72.07516479492188},{"x":2.1890220642089844,"y":74.57532501220703},{"x":1.8249759674072266,"y":76.10284423828125},{"x":1.2149677276611328,"y":78.87423706054688},{"x":0.7181968688964844,"y":81.64585876464844},{"x":0.5368631482124329,"y":84.22308349609375},{"x":0.7564245462417603,"y":87.10577392578125},{"x":0.9181066155433655,"y":88.51806640625},{"x":1.3568875789642334,"y":91.266845703125},{"x":1.9720487594604492,"y":93.9225082397461},{"x":2.869805335998535,"y":96.67609405517578},{"x":4.026493549346924,"y":99.23631286621094},{"x":4.71019172668457,"y":100.43528747558594},{"x":6.320463180541992,"y":102.67230987548828},{"x":8.292896270751953,"y":104.66864013671875},{"x":10.527410507202148,"y":106.29473876953125},{"x":13.045042037963867,"y":107.58129119873047},{"x":14.305294036865234,"y":108.06297302246094},{"x":17.058570861816406,"y":108.8245849609375},{"x":19.738489151000977,"y":109.25963592529297},{"x":22.58503532409668,"y":109.4623031616211},{"x":25.567338943481445,"y":109.36589050292969},{"x":26.990686416625977,"y":109.22407531738281},{"x":29.685686111450195,"y":108.8089370727539},{"x":32.45231628417969,"y":108.15174865722656},{"x":35.07832336425781,"y":107.26496887207031},{"x":37.63439178466797,"y":106.09351348876953},{"x":38.85661315917969,"y":105.4004898071289},{"x":41.12782287597656,"y":103.82134246826172},{"x":43.21026611328125,"y":101.93193054199219},{"x":45.010719299316406,"y":99.79389953613281},{"x":46.509918212890625,"y":97.43965148925781},{"x":47.146766662597656,"y":96.18624114990234},{"x":48.179420471191406,"y":93.58147430419922},{"x":48.905704498291016,"y":90.86083221435547},{"x":49.333900451660156,"y":88.07710266113281},{"x":49.48019027709961,"y":85.27090454101562},{"x":49.441749572753906,"y":83.87879943847656},{"x":49.21395492553711,"y":81.11614990234375},{"x":48.88788986206055,"y":78.33543395996094},{"x":48.46902847290039,"y":75.53885650634766},{"x":47.97406005859375,"y":72.78559875488281},{"x":47.701168060302734,"y":71.42537689208984},{"x":47.10042190551758,"y":68.69523620605469},{"x":46.43019104003906,"y":65.95223999023438},{"x":45.70833206176758,"y":63.24463653564453},{"x":44.95276641845703,"y":60.60857391357422},{"x":44.555789947509766,"y":59.28700256347656},{"x":43.72590637207031,"y":56.636985778808594},{"x":42.85209655761719,"y":53.9801025390625},{"x":41.9382209777832,"y":51.316246032714844},{"x":40.98863983154297,"y":48.64683532714844},{"x":40.50154113769531,"y":47.30971145629883},{"x":39.50566101074219,"y":44.632041931152344},{"x":38.502933502197266,"y":41.99885940551758},{"x":37.49367904663086,"y":39.39869689941406},{"x":36.46803283691406,"y":36.79609298706055},{"x":35.950225830078125,"y":35.49406814575195},{"x":34.90724563598633,"y":32.88964080810547},{"x":33.857139587402344,"y":30.28421401977539},{"x":32.8034782409668,"y":27.67803955078125},{"x":31.75078010559082,"y":25.073699951171875},{"x":31.22544288635254,"y":23.771133422851562},{"x":30.172632217407227,"y":21.14937973022461},{"x":29.123023986816406,"y":18.512771606445312},{"x":28.087501525878906,"y":15.879325866699219},{"x":27.070383071899414,"y":13.25079345703125},{"x":26.569618225097656,"y":11.937751770019531},{"x":25.586828231811523,"y":9.316398620605469},{"x":24.63152313232422,"y":6.699913024902344},{"x":23.708255767822266,"y":4.0907440185546875}
],
  config: {
    numBulbs: 5,
    render: {
      height: 400,
      width: 400,
      startX: 60,
      y: 0,
      spacing: 70,
      lineWidth: 2.5,
      imgHeight: 110,
      wireframe: false,
      background: '#fc0'
    },
    lengths: {
      min: 40,
      max: 130
    },
    wind: {
      major: {
        duration: {
          min: 8000,
          max: 12000
        },
        min: -2,
        max: 2
      },
      minor: {
        duration: {
          min: 800,
          max: 1200
        },
        min: 0.8,
        max: 1.2
      }
    },
    light: {
      duration: {
        min: 5000,
        max: 15000
      },
      delay: 500,
      chance: 1
    }
  },
  current: {
    elPerBulb: 0,
    wind: {
      major: 0,
      minor: 1
    },
    light: false,
    lightStatus: []
  },
  wind: {
    major: function() {
      let self = Lightbulbs
      let current = self.rand(self.config.wind.major.min, self.config.wind.major.max)
      let duration = self.rand(self.config.wind.major.duration.min, self.config.wind.major.duration.max)
      anime({
        targets: self.current.wind,
        major: current,
        duration: duration,
        loop: false,
        complete: function() {
          self.wind.major()
        }
      });
    },
    minor: function() {
      let self = Lightbulbs
      let current = self.rand(self.config.wind.minor.min, self.config.wind.minor.max)
      let duration = self.rand(self.config.wind.minor.duration.min, self.config.wind.minor.duration.max)
      anime({
        targets: self.current.wind,
        minor: current,
        duration: duration,
        loop: false,
        complete: function() {
          self.wind.minor()
        }
      });
    }
  },
  light: function() {
    let self = Lightbulbs
    let index = Math.floor(self.rand(0, self.config.numBulbs / self.config.light.chance))
    self.current.light = index > self.config.numBulbs - 1 ? false : index + 1

    let duration = self.rand(self.config.light.duration.min, self.config.light.duration.max)
    setTimeout(function() {
      self.current.light = false
      setTimeout(function() {
        self.light()
      }, self.config.light.delay)
    }, duration)
  },
  rand: function(max, min) {
    return Math.random() * (max - min) + min;
  }
}

setTimeout(function() {
  Lightbulbs.wind.major()
  Lightbulbs.wind.minor()
  Lightbulbs.light()
}, 2000)

Lightbulbs.matter = function() {
  var Engine = Matter.Engine,
      Render = Matter.Render,
      Runner = Matter.Runner,
      Events = Matter.Events,
      Body = Matter.Body,
      World = Matter.World,
      Bodies = Matter.Bodies,
      Constraint = Matter.Constraint,
      MouseConstraint = Matter.MouseConstraint,
      Mouse = Matter.Mouse,
      Vertices = Matter.Vertices;
  
  let self = Lightbulbs
  
  Matter.use('matter-attractors');
  
  var engine = Engine.create();
  engine.world.gravity.scale = 0.002;
  
  var render = Render.create({
    element: document.body,
    engine: engine,
    options: {
      wireframeBackground: '#222',
      background: self.config.render.background,
      width: self.config.render.width,
      height: self.config.render.height,
      wireframes: self.config.render.wireframe
    }
  })
  
  Render.run(render)
  
  // create runner
  var runner = Runner.create()
  Runner.run(runner, engine)

  let ballSettings = []
  for (let i = 0; i < self.config.numBulbs; i++) {
    ballSettings.push({
      length: self.rand(self.config.lengths.min, self.config.lengths.max)
    })
  }
  
  let bulbs = []
  var group = Body.nextGroup(true)
  
  for (let i = 0; i < ballSettings.length; i++) {
    let x = self.config.render.startX + i * self.config.render.spacing
    
    var bulb = Bodies.fromVertices(x, self.config.render.y - ballSettings[i].length + self.config.render.imgHeight / 2, self.bulbVertices, {
      frictionAir: 0.03,
      render: {
        sprite: {
          texture: 'https://covey-alistair.surge.sh/bulb-small.png'
        }
      }
    }, false)
    
    var litBulbOffset = 22
    var litBulb = Bodies.circle(x, self.config.render.y - ballSettings[i].length + self.config.render.imgHeight / 2 + litBulbOffset, 40, {
      collisionFilter: 0,
      render: {
        visible: false,
        sprite: {
          texture: 'https://covey-alistair.surge.sh/lit-bulb-small.png'
        }
      }
    })
    var litBulbCons = Constraint.create({
      bodyA: bulb,
      pointA: {
        x: 0,
        y: litBulbOffset
      },
      bodyB: litBulb,
      length: 0,
      render: {
        visible: false
      }
    })
    
    let cons = Constraint.create({
      pointA: {x: x, y: self.config.render.y},
      bodyB: bulb,
      pointB: {
        x: 0,
        y: -(self.config.render.imgHeight / 2) - 10
      },
      stiffness: 0.001,
      length: ballSettings[i].length,
      render: {
        anchors: false,
        strokeStyle: '#000',
        lineWidth: self.config.render.lineWidth,
        type: 'line'
      }
    })
    var light = []
    light.push(cons)
    light.push(litBulb)
    light.push(litBulbCons)
    light.push(bulb)
    self.current.elPerBulb = light.length
    
    bulbs.push(...light)
  }
  
  // add mouse control
  var mouse = Mouse.create(render.canvas),
      mouseConstraint = MouseConstraint.create(engine, {
        mouse: mouse,
        constraint: {
          stiffness: 0.01,
          render: {
            visible: false
          }
        }
      })
  
  var windPoint = Bodies.rectangle(0, self.config.render.height / 3, 5, 5, {
    isStatic: true,
    collisionFilter: 0,
    render: {
      visible: false
    },
    plugin: {
      attractors: [
        function(bodyA, bodyB) {
          return {
            x: (bodyA.position.x - bodyB.position.x) * self.current.wind.major * self.current.wind.minor * 1e-6,
            y: (bodyA.position.y - bodyB.position.y) * self.current.wind.major * self.current.wind.minor * 1e-6
          }
        }
      ]
    }
  })
  World.add(engine.world, windPoint);
 
  render.mouse = mouse
  World.add(engine.world, mouseConstraint);

  World.add(engine.world, [...bulbs]);
  
  Events.on(runner, 'afterUpdate', () => {
    for (let j = 0; j < bulbs.length; j += self.current.elPerBulb) {
      let k = j / self.current.elPerBulb
      
      let rope = bulbs[j]
      let bulb = bulbs[j + self.current.elPerBulb - 1]
      let length = ballSettings[k].length
      
      let yOffset = bulb.position.y - self.config.render.y - length
      let range = yOffset < 0 ? 0 : yOffset
      rope.stiffness = 0.001 + (0.1 / length * range)
      
      let litBulb = bulbs[j + 1]
      let lit = false
      if (self.current.light && (self.current.light - 1) * self.current.elPerBulb === j) {
        lit = true
      }
      if (lit) {
        lightOn(k, litBulb)
      } else {
        lightOff(k, litBulb)
      }
    }
  })
  
  return {
    engine: engine,
    runner: runner,
    render: render,
    canvas: render.canvas,
    stop: function() {
      Matter.Render.stop(render);
      Matter.Runner.stop(runner);
    }
  };
}

function lightOn(i, bulb) {
  if (!Lightbulbs.current.lightStatus[i]) {
    Lightbulbs.current.lightStatus[i] = true
    bulbAnimation(bulb, true)
  }
}

function lightOff(i, bulb) {
  if (Lightbulbs.current.lightStatus[i]) {
    Lightbulbs.current.lightStatus[i] = false
    bulbAnimation(bulb, false)
  }
}

function bulbAnimation(bulb, final) {
  let delay = Lightbulbs.rand(300, 700)
  let duration = Lightbulbs.rand(400, 600)
  let loops = Math.floor(Lightbulbs.rand(1.5, 3.5)) * 2 - 1
  anime({
    endDelay: delay,
    duration: duration,
    direction: 'alternate',
    loop: loops,
    loopComplete: function() {
      bulb.render.visible = !bulb.render.visible
    },
    complete: function() {
      bulb.render.visible = final
    }
  })
}

MatterTools.Demo.create({
  toolbar: {
    title: 'matter-js',
    url: 'https://github.com/liabru/matter-js',
    reset: true,
    source: true,
    fullscreen: true,
    exampleSelect: true
  },
  preventZoom: true,
  resetOnOrientation: true,
  examples: [
    {
      name: 'Hanging Lightbulbs',
      id: 'hangingLightbulbs',
      init: Lightbulbs.matter
    }
  ]
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
  2. https://cdn.rawgit.com/liabru/matter-js/d727e8601e689a3aeffd386d6ede3a16243563da/build/matter.js
  3. https://cdn.rawgit.com/liabru/matter-tools/d31e212a046b3f8279124aaa43589d27c20d261d/build/matter-tools.demo.js
  4. https://cdn.jsdelivr.net/npm/matter-attractors@0.1.6/build/matter-attractors.min.js
  5. https://cdn.jsdelivr.net/npm/animejs@3.1.0/lib/anime.min.js