[JS] Problem ze zmianą IMG.src w FF

0

mam taki oto problem ze zmianą src dla w FireFox

zmiany dokonuje w funkcji na zasadzie:
-zmiana obrazka na 'wait'
-prawdziwe zadanie funkcji np grayscale
-zmiana obrazka na 'ok'

problem:
obrazek zmienia sie dopiero po zakończeniu działania funkcji:/
jak zmusić FF do update'a src w trakcie działania funkcji??

function grayscale()
{
var stat=document.getElementById('statusTxt');
stat.innerHTML="Czekaj...";
var imag=document.getElementById('statusImg');
imag.src="obrazy/stat_wait.gif";
//tutaj prawdziwe zadanie funkcji
stat.innerHTML="Gotowe.";
imag.src="obrazy/stat_ok.gif";
}

kontrola błędów niepokazuje żadnych błędów

0

Czy na pewno "prawdziwe zadanie funkcji" wykonuje się wystarczająco długo, byś zdążył zaobserwować obrazek stat_wait? Niektórzy nie zdają sobie np. sprawy jak działa setTimeout i umieszczają wywołanie tej funkcji w tamtym miejscu. Myślą, że to sprawi, że kod będzie się tam wykonywał dłużej, synchronicznie. Tak jednak nie jest, bo setTimeout działa asynchronicznie.

Jeśli naprawdę masz jakąś długą, synchroniczną akcję w miejscu komentarza, to możesz mieć problem. JavaScript jest jednowątkowy. Być może w Firefoksie cały silnik renderujący jest jednowątkowy. I widok odświeża się dopiero po wykonaniu całego JavaScriptu.

Zakładając, że naprawdę masz czasochłonne, synchroniczne obliczenia, mogę zaproponować zamianę kodu na (troszkę) asynchroniczny. Czyli nie taki, który wykonuje się ciurkiem, nie dając przeglądarce czasu na wytchnienie i odświeżenie interfejsu. Możesz zrobić np. tak:

przeprowadz_inicjalizacje();
ustaw_obrazek('czekaj.gif');
setTimeout(function() { // (*)
  wykonaj_prawdziwe_obliczenia();
  ustaw_obrazek('gotowe.gif');
}, 10); // 10 to po prostu jakaś wzięta z sufitu mała wartość,
        // może nawet zadziała zero

Najpierw normalnie ustawiamy obrazek oznaczający ładowanie. Dzięki temu, że stosujemy setTimeout, dalsza część kodu zamknięta w funkcji anonimowej (*) zostanie wykonana z małym opóźnieniem. Ja tam wstawiłem 10ms, ale może i 1 lub 0 by wystarczyło (sprawdź sobie). W każdym razie po zmianie obrazka na "czekaj.gif" przeglądarka ma chwilę, by odświeżyć widok i pokazać właściwy obrazek.

Zauważ jednak, że jeśli obliczenia są naprawdę czasochłonne, to na czas ich trwania interfejs może być całkowicie zamrożony i będzie to widoczne dla użytkownika (i niemiłe). Jeśli np. czekaj.gif jest animowany, to przeglądarka może nie wyświetlać animacji.

Z tego powodu JavaScript nie nadaje się do przeprowadzania bardzo czasochłonnych obliczeń. Wynika to z jego wielowątkowości. Trudno powiedzieć, czy to jest błąd języka. Bezpieczna obsługa wielu wątków jest skomplikowana. Nawet gdyby język udostępniał programiście jakieś mechanizmy ułatwiające kodowanie wielowątkowe, to programista i tak musiałby uważać i znać się na rzeczy. Zwykle wielowątkowość w JavaScripcie zupełnie nie jest potrzebna. I o całe mnóstwo rzeczy po prostu nie musimy się martwić wiedząc, że wszystko i tak stanie się w jednym wątku.

0

niestety setTimeout() nie pomógł:(

nadal obrazek zmienia się dopiero po zakończeniu funkcji
operacje są czasochłonne, a niektóre może nawet baaaardzo czasochłonne
jest to skrypt z mojej stronki http://www.dumah.yoyo.pl/

czekam na inne propozycje, a tymczasem użytkownicy FF po prostu nie będą widzieć obrazka informującego, że skrypt pracuje

tak se myśle, że freez interfejsu jest wystarczającą informacją, że skrypt działa:P

0

Kiedy ten kod jest wykonywany? Co mam konkretnie kliknąć? Poklikałem w kilka losowych rzeczy, ale u mnie wszystko chodziło na tyle żwawo, że nie zarejestrowałem jakichś specjalnych opóźnień (pod Fx)... Na Odcienie szarości w lewym panelu?

Btw. naprawdę nie widzę opcji, żeby setTimeout tutaj nie pomógł. Problem pewnie leży gdzieś indziej, tylko żeby go zdebugować musiałbym spowolnić swój komp lub (prościej) skrypt... Hmm...

edit: Spowolniłem Twój skrypt i przetestowałem, rzeczywiście "loading" nie działał. Ale wprowadziłem do kodu te zmiany, o których pisałem i teraz działa. Także albo coś ściemniasz, albo czegoś nie zrozumiałeś, albo dzieją się tu jakieś czary (co jest całkiem możliwe -- chodzi w końcu o JavaScript i przeglądarki). Oto zmodyfikowany, działający kod:

function grayscale()
{
var stat=document.getElementById('statusTxt');
stat.innerHTML="Czekaj...";
var imag=document.getElementById('statusImg');
imag.src="obrazy/stat_wait.gif";

setTimeout(function() {
var mycan=document.getElementById('myCanvas');
var ctx=mycan.getContext('2d');
var imgdata=ctx.getImageData(0,0,mycan.width,mycan.height);
var pix=imgdata.data;
var s;

for (var i=0; i<pix.length; i+=4)
 {
  s=(pix[i]+pix[i+1]+pix[i+2])/3;
  pix[i]=s;
  pix[i+1]=s;
  pix[i+2]=s;
 }
ctx.putImageData(imgdata,0,0);
stat.innerHTML="Gotowe.";
imag.src="obrazy/stat_ok.gif";
}, 10);

}

Dokonałem minimalnych zmian w Twoim kodzie, nie zmieniając nawet (niestarannego, niestety!) formatowania. Zmiany te dokładnie odpowiadają temu, co napisałem we wcześniejszym poście. I naprawiają Twój problem.

0

faktycznie działa:P
roche nie zrozumiałe o co ci chodziło
robiłem coś takiego:

 <button onclick='czekaj();setTimeout(grayscale(),10);gotowe();' class='full' >odc. szarości</button>

my bad,
thx za pomoc:D
PS: co jest nie halo z moim formatowaniem JS? i jak je poprawić?

0

Nie obkminiasz działania setTimeout. Czyżbyś wcześniej kodował w innym języku niż JS? Twój kod jest dość zaawansowany jeśli chodzi o funkcjonalność. Algorytmy są stosunkowo skomplikowane. Musisz więc chyba umieć programować, choćby na Javie (chyba że kod robiłeś metoda kopiuj/wklej bez zrozumienia). Nie wykorzystujesz jednak nawet połowy możliwości, jakie daje JavaScript. Wbrew pozorom JavaScript to nie taki zwykły język. W nim programowanie funkcyjne ma bardzo duże znaczenie. I stwarza olbrzymie możliwości, praktycznie niedostępne w Javie, czy C++ (choć Java zapewnia odrobinę jako-takiej funkcyjności za pomocą anonimowych interfejsów).

Sam pomysł z rozbiciem funkcji na czekaj() i gotowe() jest fajny. Ale właśnie z powodu nie zrozumienia działania setTimeout i programowania funkcyjnego to Ci nie działa.

W wywołaniu setTimeout(f, n) f powinno być funkcją, która zostanie wywołana za około n milisekund. To jedyne co robi setTimeout. Samo wywołanie setTimeout(f, n) nie wykonuje się tych n milisekund, to nie działa tak jak delay. To tylko dopisuje funkcję f do jakby JavaScriptowego harmonogramu z czasem wykonania "n milisekund w przyszłości". I kod leci dalej. Czyli jak masz:

a();
setTimeout(f, 100);
b();

To kod wykonuje się tak:
-Najpierw a().
-Potem setTimeout notuje silnikowi przeglądarki, że za 100 ms ma wykonać f.
-Potem (natychmiast!) wykonuje się b()
(... mija ok. 100 ms)
Silnik JavaScript wykonuje zapisaną wcześniej w harmonogramie funkcję f.

Ty popełniłeś jeszcze jeden błąd. Zamiast przekazać do setTimeout funkcję grayscale, przekazałeś wynik działania funkcji grayscale. Niepotrzebnie wstawiłeś nawiasy. Zrobiłeś jakby coś takiego:

setTimeout(f(), 10);

A powinieneś:

setTimeout(f, 10);

W sumie tak naprawdę do setTimeout chcesz przekazać jako parametr f funkcję, która a) wywoła grayscale() i b) wywoła gotowe(). Mógłbyś stworzyć sobie funkcję np. o nazwie fff, która to zrobi, a potem użyć jej tylko raz przekazując ją do setTimeout, o tak:

function fff() {
  grayscale();
  gotowe();
}
setTimeout(fff, 10);

Ale po co zaśmiecać przestrzeń nazw, skoro JavaScript jest potężnym językiem funkcyjnym i pozwala na łatwe tworzenie funkcji anonimowych? (funkcji bez nazwy, których możemy użyć tylko raz) Można to zrobić tak:

setTimeout(function() {
  grayscale();
  gotowe();
}, 10);

Czyli w sumie:

<button onclick='czekaj(); setTimeout(function() { grayscale(); gotowe(); } ,10);' class='full' >odc. szarości</button>

Co do formatowania kodu to to temat rzeka. Dotyczy to każdego języka, nie tylko JS. Choć w JavaScripcie jest parę kruczków, które nie są ważne w innych językach. Np. w JS nie powinno się umieszczać znaków { w nowej linii, o tak:

if (a === b)
{
}
else
{
}

Zamiast tego powinno się zrobić tak:

if (a === b) {
} else {
}

W innych językach jest pod tym względem w dowolność. Tymczasem na skutek niefortunnych założeń w JavaScripcie, gdy zrobisz coś takiego (w funkcji zwracasz jakiś obiekt):

return 
{
  str: 'abc',
  num: 123
}

To zostanie to zamienione na (zauważ średnik za return):

return;
{
  str: 'abc',
  num: 123
};

Ten dodatkowy średnik oczywiście ma katastrofalne skutki -- funkcja nie zwraca nic (undefined). Dlatego nie należy pisać { w nowej linii. Średnik nie będzie dodany, gdy napiszesz to w ten sposób:

return {
  str: 'abc',
  num: 123
};

Podstawą formatowania są oczywiście wcięcia. Tutaj Twój kod, który na szybko sformatowałem:

function grayscale() {
  var stat = document.getElementById('statusTxt'),
      imag = document.getElementById('statusImg');
  stat.innerHTML = "Czekaj...";
  imag.src = "obrazy/stat_wait.gif";
  setTimeout(function() {
    var mycan   = document.getElementById('myCanvas'),
        ctx     = mycan.getContext('2d'),
        imgdata = ctx.getImageData(0, 0, mycan.width, mycan.height),
        pix     = imgdata.data,
        s;  
    for (var i = 0; i < pix.length; i+=4) {
      s = (pix[i] + pix [i + 1] + pix[i + 2]) / 3;
      pix[i] = s;
      pix[i + 1] = s;
      pix[i + 2] = s;
    }
    ctx.putImageData(imgdata, 0, 0);
    stat.innerHTML = "Gotowe.";
    imag.src = "obrazy/stat_ok.gif";
  }, 10);
}

Już wygląda to inaczej. Ja osobiście korzystam z nieco innego formatowania, niż to powyżej. Trochę brzydszego, ale jeszcze bardziej nastawionego na to, by każda literówka była od razu widoczna lub powodowała błąd składni (który łatwo wykryć).

0

dzięki porównaniu setTimeout() i delay() wreszcie zrozumiałem czemu to nie działało
byłem przekonany, że setTimeout() działa na zasadzie "zatrzymaj program na podany czas i zrób potem co podano", teraz wiem, że tak nie jest

wcześniej pisałem troche w C++, a nawyki co do formatowania wyniosłem z lekcji informatyki w liceum:P
od dzisiaj będe się starał pisać poprawniej kody w JS

co do wykożystywania metody Copiego Paste'a, to raczej wolę jej unikać
a jeżeli coś kopjuje to rozkminiam to tak długo, aż rozpracuje należycie

te funkcje naposałem samodzielnie,
jedynie sposób "wydobycia" kontekstu z płutna i odwoływania sie do poszczególnych pixeli zaczerpnołem ze specyfikacji html5

jeszcze raz dzięki za pomoc
pozdro

0

Nie ma sprawy. Normalnie tu nie pomagam(y) tak szczegółowo, bo ludzie i tak tego nie czytają ze zrozumieniem / chcą tylko gotowy kod do wklejenia. Jeśli jednak ktoś stara się to zrozumieć -- tak jak Ty -- to wtedy warto poświęcić chwilę i wytłumaczyć.

Z tą różnicą pomiędzy setTimeout a pascalowym Delayem to wiele osób zalicza wpadkę. Taki model jak w setTimeout, zwany asynchronicznym (tj. podana jako parametr funkcja wykona się "kiedyś w przyszłości"), w JavaScripcie występuje dość często. Np. gdy pisze się kod ajaxowy. Choć zapis bywa inny (w ajaxowym obiekcie XmlHttpRequest nie przekazujesz funkcji f jako parametr, tylko podstawiasz ją pod własność onreadystatechange).

"od dzisiaj będe się starał pisać poprawniej kody w JS"
Ja programuję już od dość dawna i uważam JavaScript za mój mocny punkt, ale wciąż odnajduję jakieś usprawnienia. Najważniejsze to nie spoczywać na laurach i ewoluować.

"te funkcje naposałem samodzielnie,
jedynie sposób "wydobycia" kontekstu z płutna i odwoływania sie do poszczególnych pixeli zaczerpnołem ze specyfikacji html5"
Ale tutaj nie masz się czego wstydzić. Tak się właśnie robi. Wzięcie, zrozumienie i zamiana kodu z przykładów to nie grzech, a korzystanie z samej specyfikacji to wręcz dobry uczynek :D.

Btw., po raz sześćset sześćdziesiąty i szósty polecam w tym dziale najlepszą książkę do poznania JavaScriptu (samego języka), jaka istnieje: "JavaScript - Mocne strony". Autorem jest Douglas Crockford, twórca formatu JSON i współtwórca specyfikacji ECMAScript (czyli po prostu JavaScriptu). Książka dość cienka, niedroga (~35 zł) i absolutnie bezcenna. Uczy m.in. programowania funkcyjnego w JavaScripcie. Jak ktoś potrafi tworzyć w miarę złożony kod i chce to zrobić w JavaScripcie, w dodatku efektywnie, to to pozycja dla niego.

0

Witam,

a ja mam taki problem, że chciałbym żeby ciężki for miał wpływ na inny obiekt poza tym loopem.

A konkretniej.

function duzaFunkcja(){
  for(var i = 0; i<miliard.toString(); i++)
      {
         // jakas instrukcja
         progres.addValue();
      }
}

gdzie progres jest obiektem funkcji obsługującej progress bara

Gdy opalę progres bara a następnie setTimeout("duzaFunkcja", 0) przegladarka się nie zwiesza, ale nie widzę rezultatów progres.addValue(). Aktualizuje się dopiero po wykonaniu fora na 100%.

0

z powyższych postów wnioskuję, że odświeżenie następuje po skończeniu kodu JS, więc przychodzi mi do głowy tylko taki pomysł aby FOR'a podzielić na x części (np. na 100, co da nam "ruchy" progres bara o 1%) i po wykonaniu każdej z nich będzie aktualizowany progres bar

jeśli coś takiego Cię zadowoli to w edycji tego posta przedstawię co naskrobałem do tej pory

//edit - odnośnie poniższego postu
zasugerowałem się na wstępie zmienną miliard.toString() i z tego powodu wolałbym pociosać FOR'a na (powiedzmy, max) 100 części, aby wywoływań setTimeout() nie było za dużo, a do tego potrzebuję parę dodatkowych zmiennych, które wrzuciłbym np. do obiektu progres bara (zakładając, że pomiędzy kolejnymi wykonaniami kodu te zmienne nie straciłyby swoich wartości (tego nie wiem bo dawno nic nie pisałem i nie mam tego w pamięci jak się zachowuje JS), te zmienne wraz z paroma funkcjami to:

var start = 0;
var end = 0;

function getStart() //nie wymaga tłumaczenia kodu
function getEnd() //tu też oczywisty kod
function moveStart(x) //zwiększa zmienną start o wartość x
function moveEnd(x) //zwiększa zmienną end o wartość x
function setEnd(x) //ustawia zmienną end na podaną wartość x

teraz reszta kodu by wyglądała mniej więcej tak:

function duza_funkcja() {
  progres.moveEnd(round(miliard.toString()/100));
  if (progres.getEnd() > miliard.toString()) {
    progres.setEnd(miliard.toString());
  }
  for (; progres.getStart() < progres.getEnd(); progres.moveStart(1)) {
    progres.addValue();
  }
  if (progres.getStart() < miliard.toString()) { //jeśli cała pętla nie dobiegła końca, to
    setTimeout(duza_funkcja, 1); //wywołaj jej następną część
  }
}

oczywiście pomijam to czy miliard.toString() zwraca liczbę i jakieś niedopatrzenia w kodzie mogą się zdarzyć, napisałem to z głowy i chodzi tu raczej o pomysł niż gotowy kod

można też pierwszy blok kodu zawrzeć w drugim (w funkcji duza_funkcja()) i niech tam sobie siedzą zmienne 'sterujące' częściami pętli wraz ze swoimi getterami, setterami i innymi move'rami ;)
pzdr, fan U ;)

0

Kod JavaScript, który się wykonuje ciurkiem, blokuje całą przeglądarkę. Możesz sobie wywoływać funkcję odświeżającą pasek postępu do woli i ona faktycznie zostanie odpalona, ale przeglądarka nie narysuje zmian (czyli nie narysuje paska postępu!) dopóki nie skończy wykonywać bloku kodu JS. Czyli całej dużej pętli :).

Dlatego takie czasochłonne rzeczy dobrze jest kodować asynchronicznie.

Załóżmy, że kod wewnątrz pętli zależy od jej licznika. Czyli masz coś takiego:

function duza_funkcja() {
  inicjalizuj();
  for (var i = 0; i < n; i++) {
    krok(i);
  }
  zakoncz();
}

I teraz zmieniasz to na "asynchroniczny kod rekurencyjny" (termin wymyśliłem na poczekaniu; chyba brzmi wystarczająco niezrozumiale by się przyjąć, prawda?). Czyli kodujesz to rekurencyjnie, ale tak, by następne wywołanie funkcji wykonującej krok pętli zostało odpalane przez setTimeout, np. po 50 milisekundach. Być może wystarczy nawet 0 milisekund, by dać przeglądarce chwilę na odetchnięcie i przerysowanie paska postępu.

function duza_funkcja_asynchronicznie() {
  inicjalizuj();
  var i = 0;
  var n = 1000;
 
  wykonaj_krok();
  
  function wykonaj_krok() {
    if (i < n) {
      krok(i);
      i++;
      setTimeout(wykonaj_krok, 50);
    } else {
      zakoncz();
    }
  }

}

To jest właśnie to "podzielenie JS-u na ileś części", o którym wspomniał @Skaarj (BTW: fan Unreala? :) ). Dopiero setTimeout powoduje, że pomiędzy krokami przeglądarka ma rzeczywiście chwilę przerwy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1