7
\$\begingroup\$

I've built this simple interactive scenario. Now everything seems working fine except that when start for loop as below. What can I do to make the script faster?

Here is the full code: http://codepen.io/Angussimons/pen/oZmNer

for (let i = 0; i < manNumber; i++) {

  // Definisco quale strada
  if (i < (manNumber * leftStreet)) {
    street[i] = document.getElementById('path-2');

  } else {
    street[i] = document.getElementById('path-1');
  }

  // genero una velocità per ognuno
  _speed[i] = Math.floor((Math.random() * 400) + 1);
  _speed[i] = (_speed[i]/1000);

  let shiftTop = -1.5;

  // genero una posizione su ognuno
  let manPosition = shiftTop + Math.floor((Math.random() * 1000) - 500)/1000;

  // vario la durata dei gesti
  var _duration = Math.floor((Math.random() * 1000) + 500);

  mansHead.push( new mojs.Shape({
    parent:           '.omini',
    className:        'omino man-head-'+i,
    shape:            'circle',
    radius:           8,
    fill:             manColor,
    top:              manPosition+'%',
    left:             0,
    x:                {0 : 5},
    y:                10,
    easing:           'linear.none',
    duration:         _duration,
  }).then({
    x:                {5 : 0},
  }));
  mansBody.push(new mojs.Shape({
    parent:           '.man-head-'+i,
    className:        'man-body-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           10,
    strokeWidth:      13,
    strokeLinecap:    'round',
    angle:            {85 : 95},
    x:                {5 : -2},
    y:                30,
    easing:           'linear.none',
    duration:         _duration,
  }).then({
    angle:            {95 : 85},
    x:                {[-2] : 5},
    y:                30,
  }));
  mansArmL.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-arm-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           10,
    strokeWidth:      6,
    strokeLinecap:    'round',
    angle:            {45 : -45},
    x:                -5,
    y:                {8 : -8},
    duration:         _duration,
  }).then({
    angle:            {[-45] : 45},
    y:                {[-8] : 8},
  }));
  mansArmR.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-arm-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           10,
    strokeWidth:      6,
    strokeLinecap:    'round',
    angle:            {[-45] : 45},
    x:                -5,
    y:                {[-8] : 8},
    duration:         _duration,
  }).then({
    angle:            {45 : -45},
    y:                {8 : -8},
  }));
  mansLegL.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-leg-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           12,
    strokeWidth:      7,
    strokeLinecap:    'round',
    angle:            {20 : -20},
    x:                22,
    y:                {8 : -8},
    duration:         _duration,
  }).then({
    angle:            {[-20] : 20},
    x:                22,
    y:                {[-8] : 8},
  }));
  mansLegR.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-leg-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           12,
    strokeWidth:      7,
    strokeLinecap:    'round',
    angle:            {[-20] : 20},
    x:                22,
    y:                {[-8] : 8},
    duration:         _duration,
  }).then({
    angle:            {20 : -20},
    x:                22,
    y:                {8 : -8},
  }));
}

1. Edit Code Description


HTML

Well the Html it's the design of the city in svg with each buildings grouped. At the end of the svg before the closing tab there are the two paths that describes the movement of the people inside this city.

HTML structure

<section id="top" class="hero home">
  <div class="main-wrapper">
    <div class="svg-container__content">
      <div class="omini">
      <svg>
      ...
      </svg>
      </div>
    </div>
  </div>
</section>

Paths

<svg>
...
<path id="path-1" d="M1920 880 890 880 640 605 500 605" style="opacity: 0; stroke: #0000FF; stroke-width: 6; fill: none;"></path>
<path id="path-2" d="M0 880 435 880 510 635 495 635" style="opacity: 0; stroke: #0000FF; stroke-width: 6; fill: none;"></path>
</svg>

CSS

Mostly are settings for colors of the svg and positions of elements. Nothing special here.

.main-wrapper {
  background: #fff6d2;
  position: relative;
}

.svg-container__content {
  position: absolute;
  width:100%;
  height:100%;
  overflow:hidden
}

#mask {
  width: 0;
  height: 0;
}

.omini {
  position: absolute;
  width: 100%;
  height: 100%;
}

.svg-container__content svg {
  width:100%;
  height:100%
}

.color-bg {
  fill:   #fff6d2;
}

#tour-eiffel {
  position: absolute;
  z-index: 1;
}

#brandev {
  position: absolute;
  z-index: 2;
}

#duomo {
  position: absolute;
  z-index: 3;
}

#street {
  position: absolute;
  z-index: 8;
}

.group {
  overflow: hidden;
}

.color-1 { fill: #989a9d; }
.color-2 { fill: #85878a; }  
.color-3 { fill: #606060; }
.color-4 { fill: #d9dbdc; }
.color-5 { fill: #f1f1f2; }
.color-6 { fill: #6b6c6f; }  
.color-7 { fill: #bdbfc1; }
.color-8 { fill: #ffffff; }
.mic-g, .belgrado-g, .nerve-centre-g, .bicocca-g, .film-space-g { cursor: pointer; }

.mic-g:hover > .color-1 { fill: #ff888e; }
.mic-g:hover > .color-2 { fill: #fc656d; }
.mic-g:hover > .color-3 { fill: #f43d47; }
.mic-g:hover > .color-4 { fill: #f7d5d9; }
.mic-g:hover > .color-5 { fill: #f1f1f2; }
.mic-g:hover > .color-6 { fill: #fd4e50; }
.mic-g:hover > .color-7 { fill: #f9b4b8; }
.mic-g:hover > .color-8 { fill: #f7f2f3; }

.film-space-g:hover > .color-1 { fill: #ff888e; }
.film-space-g:hover > .color-2 { fill: #fc656d; }
.film-space-g:hover > .color-5 { fill: #f1f1f2; }

.nerve-centre-g:hover > .color-1 { fill: #ff888e; }
.nerve-centre-g:hover > .color-2 { fill: #fc656d; }
.nerve-centre-g:hover > .color-3 { fill: #f43d47; }
.nerve-centre-g:hover > .color-5 { fill: #f1f1f2; }
.nerve-centre-g:hover > .color-8{ fill: #f7f2f3; }

.bicocca-g:hover > .color-1 { fill: #ff888e; }
.bicocca-g:hover > .color-2 { fill: #fc656d; }
.bicocca-g:hover > .color-3 { fill: #f43d47; }
.bicocca-g:hover > .color-4 { fill: #f7d5d9; }
.bicocca-g:hover > .color-8 { fill: #f7f2f3; }

.belgrado-g:hover > .color-1 { fill: #ff888e; }
.belgrado-g:hover > .color-2 { fill: #fc656d; }
.belgrado-g:hover > .color-3 { fill: #f43d47; }
.belgrado-g:hover > .color-4 { fill: #f7d5d9; }    

.modal-trans {
  z-index: 9;
}

.modal-close-btn {
  z-index: 10;
  cursor: pointer;
}
.close-btn {
  z-index: 11;
  cursor: pointer;
}

.modal__inner {
  width: 100%;
}

.modal__inner, .modal__location, .modal__link {
  z-index: 11;
  position: absolute;
  color: #252525;
}

.modal-text-class {
  z-index: 11;
}

.modal-text-title {
  font-family: monospace;
  padding: 120px 5% 20px 5%;
}

.web-link, .map-place {
  z-index: 12;
}

.modal-location p {
  font-family: monospace;
  text-align: center;
}

.modal-text-inner {
  padding: 0 5% 20px 5%;
  width: 70%;
  overflow: scroll;
  height: 80vh;
  margin-bottom: 20px;
  max-height: 600px;
}

.modal-text-class p {
  font-family: monospace;
  line-height: 2;
}

Javascript

Here the most tricky part. I'm using mo.js to animate things. That it's very light and fast as described.

The first part it's about describing custom shapes and custom easing curves:

class Website extends mojs.CustomShape {
  getShape() { return '<path d="M27.68,36.32a13.68,13.68,0,0,0,0,27.36H41.36a2.16,2.16,0,1,0,.06-4.32H27.68a9.36,9.36,0,1,1,0-18.72H42.8A9.29,9.29,0,0,1,52.16,50a2.16,2.16,0,1,0,4.32.06s0,0,0-.06A13.7,13.7,0,0,0,42.8,36.32Zm30.8,0a2.16,2.16,0,1,0,.16,4.32H72.32a9.36,9.36,0,1,1,0,18.72H57.2A9.29,9.29,0,0,1,47.84,50a2.16,2.16,0,1,0-4.32-.06s0,0,0,.06A13.7,13.7,0,0,0,57.2,63.68H72.32a13.68,13.68,0,1,0,0-27.36H58.48Z"/>'; }
}

class Mapplace extends mojs.CustomShape {
  getShape() { return '<path d="M50,6A27,27,0,0,0,23,33a32.11,32.11,0,0,0,2.13,10.72c7.71,15.69,15.4,33.44,23.09,49.16a2,2,0,0,0,3.56,0C59.56,77.2,67,59.35,74.88,43.72A31.94,31.94,0,0,0,77,33,27,27,0,0,0,50,6Zm0,4A23,23,0,0,1,73,33a29.68,29.68,0,0,1-1.87,9.28L50,87.47,28.88,42.28A29.83,29.83,0,0,1,27,33,22.93,22.93,0,0,1,50,10Zm0,8A15,15,0,1,0,65,33,15,15,0,0,0,50,18Zm0,4A11,11,0,1,1,39,33,11,11,0,0,1,50,22Z"/>'; }
}


mojs.addShape('website', Website);
mojs.addShape('mapplace', Mapplace);

const shiftCurve = mojs.easing.path( 'M0,100 C50,100 50,100 50,50 C50,0 50,0 100,0' );
const scaleCurveBase = mojs.easing.path( 'M0,100 C21.3776817,95.8051376 50,77.3262711 50,-700 C50,80.1708527 76.6222458,93.9449005 100,100' );
const scaleCurve = (p) => { return 1 + scaleCurveBase(p); };
const nScaleCurve = (p) => { return 1 - scaleCurveBase(p)/10; };
const noizeEasing = mojs.easing.path('M0,100 L24.2114672,99.7029845 L27.0786839,106.645089 L29.2555809,93.3549108 L32.0340385,103.816964 L35.3459816,94.6015626 L38.3783493,103.092634 L41.0513382,95.9547991 L43.7739944,106.645089 L45.6729927,96.8973214 L50,105.083147 L53.3504448,93.3549108 L57.7360497,103.816964 L60.8616066,95.9547991 L65.0345993,103.092634 L68.6997757,97.5106029 L71.6646194,102.03125 L75.5066986,96.5672433 L78.2949219,102.652344 L81.0313873,96.8973214 L84.0174408,102.328264 L86.0842667,97.7332592 L88.7289352,101.606306 L91.1429977,98.3533763 L94.3822556,101.287388 L97.0809174,98.7254467 L100,100');
const noizeProgress = (p) => { return 10+(5*noizeEasing(p)); };

Then there are two small section to enable click on the buildings and for the modal effect after the click:

Modal

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * MODAL CODE
 *
 */

// Parent element
const parent = document.querySelector('.main-wrapper');

// Create Element

// Inner
const modalInner = document.createElement( 'div' );
modalInner.classList.add( 'modal__inner' );
parent.appendChild( modalInner );

// Fill The Element with some html
function fillElement (ele) {
  // Inner
  modalInner.innerHTML = document.querySelector('#js-modal-template-'+ele).innerHTML;
}

const modal = new mojs.Shape({
  parent:           '.main-wrapper',
  shape:            'circle',
  className:        'modal-trans',
  fill:             '#f9d953',
  scale:            {0 : 20},
  opacity:          {0 : 1},

  delay:            500,
  duration:         1500,
  easing:           'elastic.out',
});

const modalCloseBtn = new mojs.Shape({
  parent:           '.main-wrapper',
  shape:            'circle',
  className:        'modal-close-btn',
  fill:             'white',
  radius:           20,
  scale:            {0 : 1},
  opacity:          {0 : 1},

  top:              '120px',
  left:             '90%',
  delay:            1000,
  duration:         500,
  easing:           'elastic.out',
});

const closeBtn = new mojs.Shape({
  parent:           '.main-wrapper',
  className:        'close-btn',
  shape:            'cross',
  angle:            45,
  fill:             'none',
  radius:           {0 : 10},
  opacity:          {0 : 1},
  scale:            {0 : 1},
  stroke:           '#252525',
  strokeWidth:      4,

  top:              '120px',
  left:             '90%',

  duration:         500,
  easing:           'elastic.out',
});

const modalText = new mojs.Html({
  el:               '.modal__inner',
  opacity:          {0 : 1},
  scale:            {0 : 1},
  duration:         250,
  easing:           'cubic.out',
});

const webLink = new mojs.Shape({
  parent:           '.main-wrapper',
  shape:            'website',
  className:        'web-link',
  radius:           30,
  scale:            {0 : 1},
  opacity:          {0 : 1},
  fill:             '#252525',

  top:              '360px',
  left:             '90%',
  easing:           'elastic.out',
});

const mapPlace = new mojs.Shape({
  parent:           '.main-wrapper',
  shape:            'mapplace',
  className:        'map-place',
  radius:           30,
  scale:            {0 : 1},
  opacity:          {0 : 1},
  fill:             '#252525',

  top:              '220px',
  left:             '90%',
  easing:           'elastic.out',
});


// Create the timeline for the animation of modal
const modalOpenTimeline = new mojs.Timeline({ delay: 0 });
modalOpenTimeline.append(modalCloseBtn, closeBtn, modalText, mapPlace, webLink);

Click on the buildings

Here jQuery $('el').on() function to catch clicks on building

$('svg .g').on('click' function (e) {
  fillElement('element'); // create html for modal
  modal.replay(); // replay modal timeline (the background and modal itself)
  modalOpenTimeline.replay(); //replay the opening animation (for the elements inside modal).
});

An then I repeated for each building.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * CLICK BUTTONS
 *
 */

// Cineteca
$('.mic-g').on('click', function (e) {
  fillElement('cineteca');
  modal.replay();
  modalOpenTimeline.replay();
});

// Belgrado / Kinoteka
$('.belgrado-g').on('click', function(e) {
  fillElement('kinoteka');
  modal.replay();
  modalOpenTimeline.replay();
});

// Nerve Centre
$('.nerve-centre-g').on('click', function(e) {
  fillElement('nerve-centre');
  modal.replay();
  modalOpenTimeline.replay();
});

// Bicocca
$('.bicocca-g').on('click', function(e) {
  fillElement('bicocca');
  modal.replay();
  modalOpenTimeline.replay();
});

// Film Space
$('.film-space-g').on('click', function(e) {
  fillElement('film-space');
  modal.replay();
  modalOpenTimeline.replay();
});

Animation for modal close

That it's the same process of opening modal. I'm using mojs.Html() to target also mojs.Shape() using the ClassName: 'name' property assigned previously on mojs.Shape().

$('.modal-close-btn, .close-btn').on('click', function (e) {
  const modalCloseTimeline = new mojs.Timeline();

  const mapPlaceClose = new mojs.Html({
    el:               '.map-place',
    scale:            {1 : 0},
    opacity:          {1 : 0},
    duration:         500,
    easing:           'elastic.out',
  });
  modalCloseTimeline.add(mapPlaceClose);

  const webLinkClose = new mojs.Html({
    el:               '.web-link',
    scale:            {1 : 0},
    opacity:          {1 : 0},
    duration:         500,
    easing:           'elastic.out',
  });
  modalCloseTimeline.add(webLinkClose);

  const modalCloseCloseBtn = new mojs.Html({
    el:               '.modal-close-btn',
    scale:            {1 : 0},
    opacity:          {1 : 0},
    duration:         500,
    easing:           'elastic.out',
  });
  modalCloseTimeline.add(modalCloseCloseBtn);

  const CloseCloseBtn = new mojs.Html({
    el:               '.close-btn',
    scale:            {1 : 0},
    opacity:          {1 : 0},
    duration:         500,
    easing:           'elastic.out',
  });
  modalCloseTimeline.add(CloseCloseBtn);

  const modalCloseInner = new mojs.Html({
    el:               '.modal__inner',
    scale:            {1 : 0},
    opacity:          {1 : 0},
    duration:         500,
    easing:           'elastic.out',
  });
  modalCloseTimeline.add(modalCloseInner);

  const modalCloseTrans = new mojs.Html({
    el:               '.modal-trans',
    scale:            {1 : 0},
    opacity:          {1 : 0},
    duration:         500,
    easing:           'elastic.out',
  });
  modalCloseTimeline.add(modalCloseTrans);

  modalCloseTimeline.replay();
});

Resize

// RESIZE SVG TO WINDOW mantaining aspect Ratio

function setHeight(a) {
  const k = a / 1920;
  let h = 1080 * k;
  $('.main-wrapper').width(a).height(h);
};

var w = $(window).width();
setHeight(w);

$(window).resize(function() {
  var h = $(window).height();
  var w = $(window).width();
  setHeight(w);
});

People

Here there is the part that is slow. This landscape seems quite unreal without people to do this I made:

  • a small character with a circle and 5 lines
  • a simple the "walk" (I will do a better one later)
  • a loop to generate (with some randoms) people
  • the coordinates X and Y of the path during the time where people have to stay (street)

you could change the opacity in the codepen link to see the paths.

I'll split the code to comment better

Var to control main options

const manColor      = '#3E3940';  // Color of the people;
const manScale      = 0.4;        // scale of people
const manNumber     = 30;         // number people
const leftStreet    = 0.4;        // my way of split people between the two paths
const walkDuration  = 20000;      // duration of each animation in ms

Empty arrays where store result in the generation loop

const manPosition = [];
const mansHead = [];
const mansBody = [];
const mansArmL = [];
const mansArmR = [];
const mansLegL = [];
const mansLegR = [];
const street = [];
const _speed = [];
const mans = [];

First loop where I generate people manNumber is one of the option just above to set how many little man have to be generated. leftStreet it's another and here is set to 0.4 so 40% of people will be on the path-2 (the street on the left) and the rest 60% will be on the path-1. After defining other random settings it start pushing the objects on the empty arrays with var.push(obj);

for (let i = 0; i < manNumber; i++) {

  if (i < (manNumber * leftStreet)) {
    street[i] = document.getElementById('path-2');
  } else {
    street[i] = document.getElementById('path-1');
  }
  _speed[i] = Math.floor((Math.random() * 400) + 1);
  _speed[i] = (_speed[i]/1000);

  let shiftTop = -1.5;
  let manPosition = shiftTop + Math.floor((Math.random() * 1000) - 500)/1000;
  var _duration = Math.floor((Math.random() * 1000) + 500);

  // Start generating giving a class to each "omino"
  mansHead.push( new mojs.Shape({
    parent:           '.omini',
    className:        'omino man-head-'+i,
    shape:            'circle',
    radius:           8,
    fill:             manColor,
    top:              manPosition+'%',
    left:             0,
    x:                {0 : 5},
    y:                10,
    easing:           'linear.none',
    duration:         _duration,
  }).then({
    x:                {5 : 0},
  }));
  mansBody.push(new mojs.Shape({
    parent:           '.man-head-'+i,
    className:        'man-body-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           10,
    strokeWidth:      13,
    strokeLinecap:    'round',
    angle:            {85 : 95},
    x:                {5 : -2},
    y:                30,
    easing:           'linear.none',
    duration:         _duration,
  }).then({
    angle:            {95 : 85},
    x:                {[-2] : 5},
    y:                30,
  }));
  mansArmL.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-arm-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           10,
    strokeWidth:      6,
    strokeLinecap:    'round',
    angle:            {45 : -45},
    x:                -5,
    y:                {8 : -8},
    duration:         _duration,
  }).then({
    angle:            {[-45] : 45},
    y:                {[-8] : 8},
  }));
  mansArmR.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-arm-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           10,
    strokeWidth:      6,
    strokeLinecap:    'round',
    angle:            {[-45] : 45},
    x:                -5,
    y:                {[-8] : 8},
    duration:         _duration,
  }).then({
    angle:            {45 : -45},
    y:                {8 : -8},
  }));
  mansLegL.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-leg-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           12,
    strokeWidth:      7,
    strokeLinecap:    'round',
    angle:            {20 : -20},
    x:                22,
    y:                {8 : -8},
    duration:         _duration,
  }).then({
    angle:            {[-20] : 20},
    x:                22,
    y:                {[-8] : 8},
  }));
  mansLegR.push(new mojs.Shape({
    parent:           '.man-body-'+i,
    className:        'man-leg-'+i,
    shape:            'line',
    fill:             'none',
    stroke:           manColor,
    radius:           12,
    strokeWidth:      7,
    strokeLinecap:    'round',
    angle:            {[-20] : 20},
    x:                22,
    y:                {[-8] : 8},
    duration:         _duration,
  }).then({
    angle:            {20 : -20},
    x:                22,
    y:                {8 : -8},
  }));
}

re-check the size of the element to resize image and paths proportionally

w = $('.bicocca-g')[0].getBoundingClientRect().width;
let h = $('.bicocca-g')[0].getBoundingClientRect().height;

$(window).resize(function(){
  w = $('.bicocca-g')[0].getBoundingClientRect().width;
  h = $('.bicocca-g')[0].getBoundingClientRect().height;
});

Here it starts moving people around with the class omino set before className: 'omino man-head-'+i,. I'm using Math.random() to set the direction of each man, if it's 1 play onward and if 0 play backward.

$('.omino').each(function (i, kostante) {

  var PathLenght = street[i].getTotalLength();

  let name = '.man-head-'+i;
  let object = document.querySelector(name);
  let _delay = _speed[i]*5000;

  mans[i] = new mojs.Timeline();
  mans[i].add(mansHead[i], mansBody[i], mansArmL[i], mansArmR[i], mansLegL[i], mansLegR[i]);

  let shift = Math.floor((Math.random() * walkDuration) + 1);
  let direction = Math.random() >= 0.5;
  if (direction == false) {
    direction = -1;
  } else {
    direction = 1;
  }

  const movement = new mojs.Tween({
    duration:         walkDuration,
    speed:            _speed[i],
    repeat:           999999,
    direction:        direction,
    isYoyo:           true,
    easing:           'linear.none',
    backwardEasing:   'linear.none',
    onUpdate (ep, p, isForward, isYoyo) {
      // normalization
      // var v = PathLenght * (p - 1) + PathLenght;
      let v = p*PathLenght;

      w = $('.bicocca-g')[0].getBoundingClientRect().width;
      h = $('.bicocca-g')[0].getBoundingClientRect().height;

      let point = street[i].getPointAtLength(v);

      let kx = w / 1920;
      let ky = h / 1080;
      let x = point.x * kx;
      let y = point.y * ky;

      let sx = manScale * (w / 1920);

      let attr = "matrix("+sx+", 0, 0, "+sx+", "+ x +", "+ y +")";
      object.style.transform = attr;
      mans[i].play();
    },
  });

  if (direction == 1) {
    movement.play( shift );
  } else {
    shift = 20000 + shift;
    movement.playBackward( shift );
  }

});

The Problem

if I set people to an higher number (e.g 100) start running really slow and sometimes glitch. I think the problem start with generating so many object with a for loop. Maybe generating one object and repeating it 100 times or maybe something using cache could solve this problem.

\$\endgroup\$
4
\$\begingroup\$

JavaScript

The loop that generates people - an expensive bridge to cross

bridge toll

”...DOM access is actually pretty costly - I think of it like if I have a bridge - like two pieces of land with a toll bridge, and the JavaScript engine is on one side, and the DOM is on the other, and every time I want to access the DOM from the JavaScript engine, I have to pay that toll”
    - John Hrvatin, Microsoft, MIX09, in this talk Building High Performance Web Applications and Sites at 29:38, also cited in the O'Reilly Javascript book by Nicholas C Zakas Pg 36, as well as mentioned in this post

Bearing in mind that was stated more than 10 years ago and browsers likely have come along way since then, it is still wise to be aware of this.

This code block makes quite a few redundant DOM look-ups - 30 (based on the value I see assigned to manNumber) to be exact:

for (let i = 0; i < manNumber; i++) {
  if (i < (manNumber * leftStreet)) {
    street[i] = document.getElementById('path-2');
  } else {
    street[i] = document.getElementById('path-1');
  }

It would be more efficient to look up those two elements outside the loop - preferably once the DOM is ready...

const path1 = document.getElementById('path-1');
const path2 = document.getElementById('path-2');

The inside that loop, use those references:

for (let i = 0; i < manNumber; i++) {
  street[i] = i < (manNumber * leftStreet) ? path2 : path1;
}    

selecting elements to get innerHTML

The function fillElement() uses querySelector to get elements by the id attribute:

function fillElement (ele) {
  // Inner
  modalInner.innerHTML = document.querySelector('#js-modal-template-'+ele).innerHTML;
}

It isn't wrong to use querySelector to get elements by id but using getElementById() "is definitely faster" 1 (see this jsPerf test for comparison).

Redundant click handler code

$('.mic-g').on('click', function (e) {
  fillElement('cineteca');
  modal.replay();
  modalOpenTimeline.replay();
});

// Belgrado / Kinoteka
$('.belgrado-g').on('click', function(e) {
  fillElement('kinoteka');
  modal.replay();
  modalOpenTimeline.replay();
});
//And on and on for more selectors...

It would be better to observe clicks on the document and look for the class names on the target element. Those class names could be put into a mapping of class names to values to be passed to fillElement(). Something like below (untested):

const classModalMapping = {
  'mic-g': 'cineteca',
  'belgrado-g': 'kinoteka',
  //...etc. 
}
$(document).click(function(e) {
  $(e).target.classList.forEach(function(className) {
    if (className in classModalMapping) {
      fillElement(classModalMapping[className]);
      modal.replay();
      modalOpenTimeline.replay(); 
    }
  });
});

CSS

The rulesets for .modal-text-title and .modal-text-inner both have padding rules where the left and right values are identical. Those can be condensed to a single value.

padding: 120px 5% 20px 5%;

Can be condensed to:

padding: 120px 5% 20px;

And

padding: 0 5% 20px 5%;

Can be condensed to:

padding: 0 5% 20px;

1https://www.sitepoint.com/community/t/getelementbyid-vs-queryselector/280663/2

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.