I've built this simple interactive scenario. Now everything seems to be working fine except for the start of the for
loop as below. What can I do to make the script execution 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 starts with generating so many objects with a for
loop. Maybe generating one object and repeating it 100 times or maybe something using cache could solve this problem.