Is it possible to optimize this javascript code?

3

I'm making a page that works like a GPS for a character in a given game. The content of the page is nothing more than a map with a marked path, and 240 images overlap this map to "tag" the character's location.

The 240 images take 40 minutes and 35 seconds to loop (go from the start point to the end point, and return to the starting point).

With the help of Sergio in this other question , I was able to adjust my code to change the images every 10 seconds based on the current time.

Now the problem I'm facing is: my javascript has gone absurdly large, with more than 40 thousand lines, is leaving the page with approximately 1.8mb, and this can cause a slowdown for some users.

Following this logic to change the images:

function mudarYujia() {
  var currentHora = new Date().getHours();
  var currentMinuto = new Date().getMinutes();
  var currentSegundo = new Date().getSeconds();
  var img = document.getElementById("mapa_movimento");
  var base = "http://triviapw.hiperportal.blog.br/elysium_old/images/moves/";
  var prefixo = "floresta_yujia_v2-b-";
  var extensao = ".png";

if (0<= currentHora && currentHora < 1){
   if (2 <= currentMinuto && currentMinuto < 3){
    if (45 <= currentSegundo && currentSegundo < 55){
   img.src = base + prefixo + '1' + extensao;
    }}}
if (0<= currentHora && currentHora < 1){
   if (3 <= currentMinuto && currentMinuto < 4){
    if (5 <= currentSegundo && currentSegundo < 15){
   img.src = base + prefixo + '2' + extensao;
    }}}
if (0<= currentHora && currentHora < 1){
   if (3 <= currentMinuto && currentMinuto < 4){
    if (15 <= currentSegundo && currentSegundo < 25){
   img.src = base + prefixo + '3' + extensao;
    }}}
if (0<= currentHora && currentHora < 1){
   if (3 <= currentMinuto && currentMinuto < 4){
    if (26 <= currentSegundo && currentSegundo < 36){
   img.src = base + prefixo + '4' + extensao;
    }}}
if (0<= currentHora && currentHora < 1){
   if (3 <= currentMinuto && currentMinuto < 4){
    if (36 <= currentSegundo && currentSegundo < 46){
   img.src = base + prefixo + '5' + extensao;
    }}}
...


return mudarYujia;
}

setInterval(mudarYujia(), 1000); // atualizar a cada 1 segundo

The function checks the current hour, minute, and second to be able to tell which image to display at this time. All images are numbered from 1 to 240 in this format "forest_yujia_v2-b-1.png", "forest_yujia_v2-b-2.png" ...

The full code can be seen here at jsfiddle

The map used is this

Andtheimagessuperimposedonthemap,whichareexchanged,notjustanobjectusedtomarkthelocation,andfollowinthisformat,butinotherpositions

An important detail is that the character resumes the path (image "forest_yujia_v2-b-1.png") every day at 00 hours, 2 minutes and 35 seconds.

I tried to locate something on the internet that could help me reduce everything, but I did not get any results.

    
asked by anonymous 29.01.2017 / 22:23

2 answers

12

Instead of changing a mask with pin at each position, we can rethink the logic and use the same image in different positions.

The Marker (pin)

Let's start with pin :

Ifweputthepininsideacontainerwithabsoluteposition,simplychangethetopandleftpropertiesasneeded.Ofcourseinthiscase,weneedtoconsiderthe"beak" of the marker, not the upper left corner of it. This is simple to solve, just subtract the height of it from the desired point, and half the width.


The coordinates

Basically what will happen is a change of position in a two-dimensional plane, over a given time. So, instead of using 240 images, we just have to iterate through 240 pairs of x and y . In JavaScript this is easy with arrays :

var coordenadas = [ [20, 30], [25,87], ...., [98,103] ];

In the case we are using a coordinate array , each coordinate is an array of x and y . Nothing would prevent us from doing something more "simple" by alternating x and y in an array only, but for both didactic and reading, let's keep the above format.


The weather

As you will be dealing with a specific time, where the movement has an initial time and an end time, it does not make sense to use a time interval to calculate this (increment every%% of seconds%), better to use absolute time . Intervals often have a deviation over time.

Translating in JS:

segundosInicio = 2 * 60 + 35;
segundosDuracao = 40 * 60 + 35;
segundosAgora = ( Date.now() / 1000 ) % 86400;

// calculo do ciclo variando de 0 a <1
c = (( segundosAgora - segundosInicio + segundosDuracao ) % segundosDuracao )
    / segundosDuracao;

If you want a round-trip, you can use this logic:

// transformando o ciclo em posicao das coordenadas
i = Math.floor((coords.length * 2 - 2) * c);
// aqui determinamos se o movimento é de ida ou volta
if( i >= coords.length ) i = coords.length * 2 - i - 1;

If you want one-way movement (for example a circular path), this is enough:

// transformando o ciclo em posicao das coordenadas
i = Math.floor(coords.length * c);

Being n is the index used to get the correct coordinate.


Putting it all together

The code below is a demonstration of the ideas put into practice. The time of the demonstration is calculated differently from the one mentioned above, only so that it is possible to follow the algorithm in operation without wasting much time. Notice how well the code is:

var coords = [
   [ 84,270], [148,247], [145,225], [179,184], [240,132],
   [294, 86], [395, 58], [422, 99], [486,117], [516,167],
   [495,227], [509,269], [528,314], [501,376], [489,450],
   [447,504], [377,536], [292,583], [252,526], [217,459],
   [158,338], [140,295]
];

function mudarYujia() {
   // Esta formula aqui e' apenas para demonstracao no site
   // veja o topico anterior para calculo do 'i' ao longo do dia
   var seconds = Date.now() / 1000;
   var i = Math.floor(seconds % coords.length);

   var pin = document.getElementById('pin');
   pin.style.left = (coords[i][0] - 15) + 'px';
   pin.style.top  = (coords[i][1] - 45) + 'px';
}

// Alem de preparar o interval temos que chamar a funcao uma vez
mudarYujia();
setInterval(mudarYujia, 1000);
#map {
	position:relative;
	margin:0;
	padding:0;
	width:600px;
	height:757px;
	background:url(https://i.stack.imgur.com/J0tAr.jpg);
}

#pin {
	position:absolute;
	width:30px;
	height:45px;
	background:url(https://i.stack.imgur.com/x3J2d.png);
	background-size:30px 45px;
	top:0;
	left:0;
}
Clique no link "página toda" para ver melhor<br>
<div id="map"><div id="pin"></div></div>
    
30.01.2017 / 00:39
1

PS: I took some concepts presented by Bacco in the Answer above.

The first thing you need to do is to store the event date and the coordinates of the event, so you will have an object similar to the following:

var positions = [
    {"data":"2017-01-30T04:16:00.415Z","posX":79,"posY":270},
    {"data":"2017-01-30T04:16:01.350Z","posX":152,"posY":245},
    {"data":"2017-01-30T04:16:01.822Z","posX":146,"posY":218},
    {"data":"2017-01-30T04:16:02.358Z","posX":180,"posY":179},
    {"data":"2017-01-30T04:16:02.847Z","posX":245,"posY":134},
    {"data":"2017-01-30T04:16:03.334Z","posX":291,"posY":86}
]

In the example below, if you click on the map, it will store the date of the click and its position. if you click gravar , it will restart the process.

Once you have all the points, you will have to define the duration of your animation, in this case it will be the time elapsed from the first event until the last.

var primeiro = positions[0];
var ultimo = positions[positions.length - 1];
var total = ultimo.data.getTime() - primeiro.data.getTime();

The second step is to define the steps of the animation, remembering that it must go from 0% to 100% , that is, you have to apply a simple three rule. in the end it will have a css like the following.:

.animacao { animation: animacao 3093ms infinite }
@keyframes animacao {
    0.00% { top: 270px; left: 80px; }
    23.15% { top: 248px; left: 148px; }
    42.29% { top: 221px; left: 143px; }
    64.02% { top: 170px; left: 198px; }
    85.22% { top: 101px; left: 273px; }
    100.00% { top: 69px; left: 325px; }
}

In the example below, if you click on executar after creating the coordinates on the map, javaScript will generate a dynamic CSS that will reproduce your clicks.

var map = document.getElementById("map");
var gravar = document.getElementById("gravar");
var executar = document.getElementById("executar");
var positions = [];

var pin = null;
var style = null;
map.addEventListener("click", function (event) {
  var posX = document.body.scrollLeft - map.offsetLeft + event.clientX;
  var posY = document.body.scrollTop - map.offsetTop + event.clientY;
  
  if (pin) 
    pin.classList.add("transparente");
  pin = document.createElement("div");
  pin.classList.add("pin");
  pin.style.top = posY + "px";
  pin.style.left = posX + "px";
  map.appendChild(pin);
  
  positions.push({ 
    data: new Date(), 
    posX: posX, 
    posY: posY
  });
});

var removerPins = function () {
  var pins = document.querySelectorAll(".pin");
  [].forEach.call(pins, function (pin, indice) {
    map.removeChild(pin);
  });
}

gravar.addEventListener("click", function (event) {
  positions.length = 0;
  removerPins();
});

executar.addEventListener("click", function (event) {
  removerPins(); 
  var primeiro = positions[0];
  var ultimo = positions[positions.length - 1];
  var total = ultimo.data.getTime() - primeiro.data.getTime();
  
  var animation = ".animacao { animation: animacao " + total + "ms infinite }\n";
  animation += "@keyframes animacao {\n";
  var tempos = positions.forEach(function (position, indice) {
    var elapsed = position.data.getTime() - primeiro.data.getTime();
    elapsed = ((elapsed / total) * 100).toFixed(2);
    var posX = position.posX;
    var posY = position.posY;
    animation += "\t" + elapsed + "% { top: " + posY + "px; left: " + posX + "px; }\n";
  });
  animation += "}";
  var blob = new Blob([animation], { type: 'text/css' });
  var cssUrl = URL.createObjectURL(blob);
  
  if (style) 
    document.head.removeChild(style);
  style = document.createElement("link");
  style.rel = "stylesheet";
  style.type = "text/css";
  style.href = cssUrl;
  document.head.appendChild(style);
  
  pin = document.createElement("div");
  pin.classList.add("pin");
  pin.classList.add("animacao");
  map.appendChild(pin);
});
#map {
  position:relative;
  margin:0;
  padding:0;
  width:600px;
  height:757px;
  background:url(https://i.stack.imgur.com/J0tAr.jpg);
}

.transparente {
  opacity: 0.5;
}

.pin {
  position:absolute;
  width:30px;
  height:45px;
  background:url(https://i.stack.imgur.com/x3J2d.png);
  background-size:30px 45px;
  transform: translate(-50%, -100%)
}
<input id="gravar" type="button" value="gravar" />
<input id="executar" type="button" value="executar" />
<div id="map">
  
</div>
    
30.01.2017 / 05:24