Is it possible to optimize this javascript code?

Asked

Viewed 206 times

3

I’m making a page that works as 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 superimposing this map to "mark" the character’s location.

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

With the help of Sergio on this other issue, 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 become absurdly large, with more than 40,000 lines, is leaving the page at approximately 1.8mb, and this can cause a slowdown for some users.

Following is logic for exchanging 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 time, minute and second, to be able to tell which image should be displayed at this time. All images are numbered from 1 to 240 in this format "floresta_yujia_v2-b-1.png", "floresta_yujia_v2-b-2.png"...

The complete code can be seen here on jsfiddle

The map used is this

And the images superimposed on the map, which are exchanged, not just an object used to mark the location, and follow in this format, but in other positions floresta_yujia_v2-b-1.png

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

I tried to find something on the Internet that could help me narrow it down, but I didn’t get any results.

2 answers

12


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

The marker (pin)

Let’s start with the pin:

pin

If we put the pin within a container with absolute position, just change the properties top and left as necessary. Of course, in this case, we need to consider the "tip" of the marker, not the upper left corner of it. This is simple to solve, just subtract its height from the desired point, and half the width.


The coordinates

Basically what’s going to happen is a position change on a two-dimensional plane over a given time. So instead of using 240 images, we just iterate for 240 pairs of x and y. In Javascript this is easy with arrays:

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

In case we are using a array of coordinates, each coordinate being one array of x and y. Nothing would stop us doing something more "simple" by alternating x and y in a array only, but for the sake of both teaching and reading, let’s keep the format above.


The time

As you will deal with a certain time, where the movement has an initial time and a final time, it makes no sense to use a time interval to calculate this (increment each time n seconds), we better use the absolute time. Intervals usually present a deviation with time.

Translating into 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 movement, 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 a one-way motion (for example a circular path) that’s all you need:

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

Whereas i is the index used to pick 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 losing much time. Note how the code is very dry:

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>

  • I have already marked your answer as accepted, because it worked very well everything you taught me, thank you. I can already go working this way for my maps. As for working with '%' instead of 'px' I couldn’t do it myself either, the closest "responsive" I could get was the map readjusted to the width of the http://codepen.io/anon/pen/rjpQjWbrowser, but the pin was gone for any of the other alternatives I tried, you had said you could readjust later, so I’ll wait, thanks again Bacco.

  • To turn the coordinates you already have into percentage is simple: (coords[i][0]/600*100) + '%'; and (coords[i][1]/757*100) + '%'; - the only thing missing is to put in the CSS of the PIN a margin-left:-15px;margin-top:-45px to adjust the "nozzle". 600 being the image width and 757 the height.

  • can’t find what I did wrong http://codepen.io/anon/pen/pRpqMZ, I changed the javascript part and added the two parameters in the CSS, but the pin is still off the map when the page is less than 600px wide.

  • If you want it to be based on width, you better specify the #map in vw, and the pin as well. vw is percentage of width. Note that the way I did, even if you distort at the time, it works: http://codepen.io/bacco/pen/PWEVKE - I divided the duration by 10 just to make it easier to analyze.

  • I’m sorry to take up so much of your time, I’ve learned a lot from my question, this vw system is one more I’ve never heard of. seems to be working perfectly! Very grateful.

  • If you want things relative to the width of the visible area, it’s vw, if you want things relative to the height it’s vh: More details here: http://answall.com/a/168621/70 - important to note that we only use vw to maintain image ratio. The calculation of 126 was basically the value of 757 divided by 600 and multiplied by 100. If you change this value, the image distorts, but the pin will be working fine, because it is based on %, and the image occupies 100% of the rectangle, so it distorts the path the same way it distorts the image.

  • Got it, thanks again, I’ve done several tests and looked for this measurement system. Working perfectly! Thank you.

Show 3 more comments

1

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

The first thing you need to do, is store the date of the event and the coordinates of it, then 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 click date and the position of it. if you click on gravar, it will restart the process.

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

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

the second step will be to define the stages of animation, remembering that it should go from 0% to 100%, ie, you have to apply a simple three rule. at the end you 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 play 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>

  • Thanks for your alternative, I found it really interesting. Maybe I didn’t follow everything we talked about here, but I didn’t understand anything about working with the coordinates to change the image on the page, with the result that Bacco presented, is already on the final path to my goal, but I’ll look more calmly at what you’ve provided me as well. I tested here and added several points, clicked on run and the pin did the right way, but when updating the page, the information is lost and had to rewrite. Tomorrow I’ll reread to see if I missed anything, thank you very much

  • @Wesley to the "record" function is only there to simulate the events, the ideal would be that they come already filled from the server. But if you need them to be stored locally, you can use localStorage or Indexeddb.

  • @Wesley follows an example using localStorage.: https://jsfiddle.net/uyu2m1h8/

  • Here’s another feature I didn’t know, this Torage locale, thank you very much. I imagined that he would add something to the file used to record the route, but I saw nothing different in it after recording the route, in case I need to edit some of the points after recorded, it is possible to change the location only of it or I must rewrite everything?

  • @Wesley understands that Localstorage is saved on the user’s machine, but the ideal is to save it to the server. In any case, in the example I passed, it uses Storagejs, it has methods to remove and update a record by id, in your case a coordinate.

  • I understood, I really liked your proposal, can be useful to me on other occasions too, thank you again.

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.