[HTML/JS] Idea wyświetlania tekstu na stronie

0

Witam,
Chcialbym sie zapytac jak zabrac sie za zrobienie takiej ciekawej animacji podczas wypisywania tekstu. Otoz pod linkiem http://www.youtube.com/watch?v=ePDR3Pza1_g jest intro z gry Doom 3. A w nim od 12 sekundy zostaje wyswietlony tekst. Jak osiagnac taki efekt? Pytanie moze trywialne ale nie mam pomyslu jak cos takiego zrobic.

0

Chodzi o to, że tekst tak się pojawia jakby był powoli drukowany? Wstaw tekst do stringu i odpalaj funkcję wyświetlającą kolejną literę albo przy użyciu setInterval (wtedy po skończeniu wypisywania wewnątrz funkcji musiałbyś użyć clearInterval), albo setTimeout (rekurencyjnie, wewnątrz funkcji).

0

Tak o cos takiego mi chodzi. Poszukalem o setTimeout i napisalem funkcje, ktora wywoluje przycisk na stronie:

function fun()
{
	var help = new Array();
	var w, z, c;
	help[0] = "Linia pierwsza";
	help[1] = "Linia druga";
	help[2] = "Linia trzecia";
	help[3] = "Linia czwarta";
	help[4] = "Linia piata";

	for(w=0 ; w<help.length ; w++)
	{
		for(z=0 ; z<help[w].length ; z++)
		{
			//document.write(help[w][z]);
			window.setTimeout("document.write(help[w][z])", 5000);
		}
	}
}

Taki kod nie dziala. Pewnie czegos nie doczytalem/zrozumialem :/

0

Na początek parę rad ogólnych.

Nie używaj document.write. Zamiast tego wrzucaj poszczególne znaki do jakiegoś elementu, używając np. innerHTML lub metod DOM.

Jeśli chodzi o tablice, to nie używaj konstruktora Array(), tylko literału tablicowego. Jest bardziej zwięzły i przejrzysty.

Masz tak:

// niezalecane: użycie konstruktora Array()...
var help = new Array();
// i ręczne wypełnienie kolejnych elementów wartościami
help[0] = "Linia pierwsza";
help[1] = "Linia druga";
help[2] = "Linia trzecia";
help[3] = "Linia czwarta";
help[4] = "Linia piata";

Lepiej jest tak:

// literał tablicowy - zalecany
var help = ['Linia pierwsza', 'Linia druga', 'Linia trzecia', 'Linia czwarta', 'Linia piąta'];

Powyższy kod możesz też zapisać tak, jeśli linii jest dużo/są długie (to to samo, tylko wstawiłem entery):

// literał tablicowy - zalecany
var help = [
  'Linia pierwsza',
  'Linia druga',
  'Linia trzecia',
  'Linia czwarta',
  'Linia piąta'
];

Nie radzę też używać wersji setTimeout z pierwszym argumentem w postaci stringu z kodem JavaScript. Zamiast tego podaj po prostu funkcję. Nie musisz też pisać window.setTimeout. window to tzw. obiekt globalny. Używa się go tylko w dość rzadkich przypadkach. Ponieważ window to obiekt globalny, znajdujące się w nim funkcje, takie jak window.alert, czy window.setTimeout możesz równie dobrze wywoływać bez podawania tego obiektu. Zamiast "window.setTimeout" wystarczy po prostu "setTimeout".

Algorytm, który zapisałeś w kodzie jest niestety nieprawidłowy. Wywołanie setTimeout(f, t) powoduje, że funkcja f zostanie wywołana po czasie około t milisekund. setTimeout działa asynchronicznie. Znaczy to, że setTimeout tylko mówi "wykonaj to za tyle i tyle milisekund". setTimeout NIE mówi "zaczekaj w tym miejscu t milisekund i potem wywołaj f". Czyli gdy masz instrukcje:

a();
setTimeout(f, 1000);
b();
c();

To działa to tak, że wykonywana jest a(). Następnie wykonywana jest setTimeout(f, 1000). JavaScript notuje gdzieś sobie, że za 1000 milisekund musi wykonać funkcję f. Ale nie czeka tu tego 1000 ms. Natychmiast idzie dalej. Wywołuje b() i c(), co trwa zapewne tylko kilka milisekund. Czeka dalej te ponad 900 milisekund i dopiero wywołuje zanotowaną wcześniej f(). setTimeout nie działa jak Delay w Pascalu.

Spróbuj trochę pokombinować. Nie wiem, czy masz na tę chwilę taki poziom, który pozwalałby Ci na napisanie takiej animacji. Nie jest to wbrew pozorom aż tak proste. Choć sam kod jest dość zwięzły. Musisz przede wszystkim zrozumieć działanie setTimeout. Żeby to zrobić porządnie i schludnie, przydałaby Ci się też znajomość tzw. domknięć... Jeśli jesteś dopiero początkującym JavaScripterem, to to troszkę dużo. Polecić Ci mogę tę książkę, którą polecam każdemu JavaScripterowi: "JavaScript: Mocne strony" autorstwa Douglasa Crockforda. Raczej cienka i niedroga.

0

No coz jestem poczatkujacy. Postaram sie troche poglowkowac i moze cos wykombinuje. To w zasadzie pierwsze uzycie tej metody i zostal taki misz-masz.

Kod wlasciwy dla mojego projektu juz jest gotowy a teraz chodzi mi o "ubranie" tego aby jakos to wszystko ladnie wygladalo.

Te domkniecia to cos na tym etapie troche zwiekszza nieczytelnosc kodu :) Znalazlem cos takiego jak na razie https://developer.mozilla.org/pl/Nowości_w_JavaScript_1.8

0

Oj, nie o to chodzi z tymi domknięciami. Nie używaj tego, co proponuje tam Mozilla. Nie działa to we wszystkich przeglądarkach. Domknięcia po angielsku zwą się "closure(s)", możesz to pogooglować.

Jeśli mogę spytać... projekt robisz komercyjnie? Dysponujesz kontem z możliwością internetowych przelewów? (jeśli odpowiesz to się dowiesz, czemu pytam)

0

Projekt jest calosciowo niekomercyjny. Zaliczenie na zajeciach.

Jeszcze zapytanie. Poniewaz ta funkcja ma zostac wykonana po nacisnieciu przycisku na stronie i wtedy ma zostac umiejscowiona w jakims konkretnym miejscu. Tak wiec pod przyciskiem wywoluje funkcje ze skryptu. I jesli dobrze rozumie to w tej funkcji ma nie byc metody setTimeout?

0

W ten czy inny sposób musisz tam wstawić to setTimeout lub setInterval. Tak się robi animacje.

Mógłbym Ci napisać porządne rozwiązanie do przetrawienia (jestem zawodowym webdeveloperem), ale na tym forum raczej tak nie robimy -- chyba że w dziale Praca, za wynagrodzeniem. Również w ramach działań przeciwko lenistwu. Ja nie potrzebuję jednak kasy, a jakbym miał wziąć normalną stawkę godzinową, to raczej by Ci się to nie opłacało w edukacyjnym projekcie. A co Ty na to, jakbym Ci napisał taką funkcję, a ty byś potem wpłacił chociaż te 10-20 zł (czy ile tam możesz) na wybrany przeze mnie cel charytatywny? Znam pewne bardzo chore małe dziecko potrzebujące pomocy. Nawet 10 zł to jak 100 kliknięć w brzuszek Pajacyka :). Myślę, że nagięcie zasad w ten sposób nie byłoby znowu takie złe, a choćbyś z mojego rozwiązania nie skorzystał (bo np. na tym etapie jednak byś go nie ogarniał), to i tak spełniłbyś dobry uczynek. Hm?

0

Co do pomocy charytatywnej - napisz na pw co i jak.
Co do projektu - jest on wlasnie po to aby samemu przebrnac przez trudy pisania skryptu, wiec wolalbym jedynie jakies drobne sugestie czy w dobra strone zmierzam :) Skoro mam i chce sie czegos nauczyc :) Zalozenia mojego projektu juz sa spelnione a teraz to po prostu moja wlasna ambicja i chcec zrobienia czegos porzadnie i tak jak to sobie zalozylem :)

0

@metal_man:
Doskonale! Ponieważ Twoje podejście uważam za słuszne, pokażę Ci po prostu jak to można napisać. Jeśli będziesz miał jakieś pytania odnośnie kodu, to chętnie na nie opowiem. Być może będą w nim jakieś błędy (nie testowałem zbyt intensywnie), wtedy też postaram się pokazać jak można zrobić łatki.

Czemu chcę pokazać kod, a nie tłumaczyć krok po kroku? Bo jest spora szansa, że jednak nie masz już statusu początkującego i umiesz czytać kod. Po popatrzeniu na mój możesz przecież napisać własny.

Dla ułatwienia zamieszczam cały kod HTML strony testowej, choć oczywiście nas interesuje głównie kod funkcji w JavaScripcie. Funkcję nazwałem sobie typewriter, to efekt przypomina mi pisanie na maszynie :).

Aha, funkcja używa standardowych metod DOM. Gdybym użył własności innerHTML, to mógłbym z dosłownie 10 linijek zrobić 1. Ale zapis do el.innerHTML niszczy m.in. wszystkie obserwatory zdarzeń na elementach znajdujących się wewnątrz elementu el. Funkcja przez to byłaby mniej uniwersalna, a ja chciałem pokazać, jak się tworzy łatwy do ponownego użycia kod. Stąd też funkcja ma dość spore możliwości. Przykładowe sposoby na ich wykorzystanie znajdują się w zakomentowanym kodzie.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="language" content="pl" />
  <title>JS: Printed text animation (4programmers.net)</title>
  <script type="text/javascript">
//<![CDATA[
           
/**
 * Wyświetla stopniowo tekst tak, jakby był pisany na maszynie.
 * 
 * Nowe linie tekstu wymusza się poprzez wstawienie w parametr text
 * znaku nowej linii (\n).
 * 
 * W obiekcie options można ustawić następujące opcje:
 *   interval (number) - czas, który ma upłynąć pomiędzy wypisaniem kolejnych
 *                       liter (w milisekundach)
 *   random   (number) - losowość czasu pomiędzy wypisywaniem liter.
 *                       Liczba rzeczywista z zakresu <0.0..1.0>.
 *                       0.0 oznacza brak losowości (litery będą się pojawiać
 *                       równo co interval milisekund). 1.0 oznacza maksymalną
 *                       losowość (litery będą się pojawiały co 0-2*interval ms)
 *
 * @param {mixed}    container Element, do którego wstawiany będzie tekst.
 *                             Może być albo elementem DOM, albo stringiem
 *                             z ID elementu DOM.
 * @param {string}   text      Wypisywany tekst. Nie powinien zawierać kodu HTML.
 *                             Znaki nowej linii (\n) zostaną zamienione na <br>.
 * @param {object}   options   Obiekt z opcjami. Opisany wyżej (opcjonalny).
 * @param {function} done      Funkcja wywoływana po zakończeniu wyświetlania
 *                             tekstu (parametr opcjonalny).
 */  
function typewriter(container, text, options, done) {
  var interval      = 100, // wartość domyślna dla options.interval
      random        = 0.5, // wartość domyślna dla options.random
      pos           = 0,
      length        = 0,
      printNextChar = function() {
        var ch,
            delay,
            lastChild = container.lastChild;
        if (pos < length) {
          ch = text.charAt(pos);
          if (ch === '\n') {
            container.appendChild(document.createElement('br'));
          } else {
            if (lastChild && lastChild.nodeType === 3) { // 3 = TEXT_NODE
              // lepiej użyć textNode.data, bo textNode.appendData ma bugi w IE8
              lastChild.data += ch; 
            } else {
              container.appendChild(document.createTextNode(ch));
            }
          }
          pos++;
          delay = interval + random * interval * (Math.random() - 0.5);
          setTimeout(printNextChar, delay);
        } else if (typeof done === 'function') {
          done();
        }
      }
  ;
  options = options || {};
  if (typeof container === 'string') {
    container = document.getElementById(container);
  }
  if (container && container.nodeType === 1 // 1 = ELEMENT_NODE
      && typeof text === 'string') {
	  if (options.interval >= 0) {
		    interval = +options.interval;
	  }
	  if (options.random >= 0.0 && options.random <= 1.0) {
	    random = +options.random;
	  }
    length = text.length;
    printNextChar();
  } else {
	  alert('typewriter() error: First param must be a HTMLElement reference '
			  + 'or an ID string. Second param must be a string.');
  }
}

// trzeba użyć onload lub czegoś podobnego, bo element #doom musi być już gotowy
window.onload = function() {
	// przykłady użycia
	
  typewriter('doom', 'Hello world!\nDoom ]I[ ownz!\nAll your base are belong to us.');

// NOTKA: tutaj powinno się znajdować tylko jedno wywołanie typewriter,
// więc jak odkomentujesz którąś z poniższych linii, to zakomentuj pozostałe
// wywołania funkcji

// Dlugi okres pomiędzy wyświetleniem znaków  
// typewriter('doom', 'Hello world!\nDoom ]I[ ownz!\nAll your base are belong to us.', { interval: 500 });

// Równe odstępy pomiędzy znakami -- zawsze dokładnie 200 ms (random na 0!)  
// typewriter('doom', 'Hello world!\nDoom ]I[ ownz!\nAll your base are belong to us.', { interval: 200, random: 0 });

// Wyświetlenie komunikatu po wykonaniu animacji
//  typewriter('doom', 'Hello\nworld!', {}, function() { alert('Skończyłem!'); });

// Po wykonaniu animacji wyświetla komunikat, a następnie wypisuje dalszy tekst
//  typewriter('doom', 'Hello\nworld!', {}, function() {
//    alert('Skończyłem! Teraz dorzucę trochę znaków zapytania!');
//    typewriter('doom', '????????');
//  });

}

// ]]>  
  </script>  
</head>
<body>
  <h1>DOOM text!</h1>
  <!-- w #doom zostanie umieszczony tekst -->
  <div id="doom">
  </div>
</body>
</html>

PS. Wysłałem Ci PM, napisz proszę jeśli nie doszło bo w PM-ach na tym forum jestem totalnym n00bem, to była moja pierwsza :).

0

Wielkie dzieki za przyklad funkcji. Mam teraz nad czym posiedziec. Jednak znaczenie z grubsza rozumie wiec mysle, ze nie bedzie problemu z calkowitym rozumieniem. Jednak od razu rzucilo mi sie kilka linii. Wszystko dzieje zostalo zamkniete w funkcji function typewriter(). OK. Nastepnie po deklaracji zmiennych uzyles printNextChar = function() { ... }. Te zapis mnie od razu zainteresowal. Funkcja w funkcji?
No i faktycznie, kodu znacznie wiecej niz na poczatku napisalem oraz wiecej niz zaczalem ukladac :)

PM doszedl :)

0
metal_man napisał(a)

printNextChar = function() { ... } (...) Funkcja w funkcji?

Tak. To jest właśnie domknięcie. Rzecz ekstremalnie ważna w JavaScripcie. Jedna z dosłownie kilku cech stanowiących o sile tego języka. Bardzo polecam Ci to opanować.

Zaraz, domknięcie? Przecież to zwykła funkcja wewnętrzna! Tak, sam fakt, że funkcja znajduje się wewnątrz innej nie jest jeszcze domknięciem. Funkcja wewnętrzna jest widoczna tylko w zewnętrznej. Poza funkcją typewriter() nie możesz użyć printNextChar(). Tam jej nie widać. I bardzo dobrze. Bo printNextChar() to nasza funkcja pomocnicza. A co z tym domknięciem? Ano domknięciem nazywamy to, że funkcja wewnętrzna (printNextChar) widzi wszystkie zmienne zadeklarowane w funkcji zewnętrznej (typewriter). I może je modyfikować. Takie proste, ale ma olbrzymie konsekwencje. Pozwala symulować mnóstwo konstrukcji, które są dostępne w innych językach, a w JavaScripcie niby ich nie ma.

W JS nie ma np. zmiennych lokalnych statycznych. Więc jakbym zadeklarował w printNextChar zmienną pos i nadał jej wartość 0, to przy każdym wywołaniu tej funkcji miałaby ona 0. Więc każdy następny znak byłby znakiem numer 0. To niedobrze. Deklaruję jednak zmienną pos na zewnątrz, w funkcji typewriter. Zmienna ta jest inicjalizowana tylko przy wywołaniu typewriter. Potem, gdy wewnątrz typewriter wywoływana jest wielokrotnie printNextChar, to zmienna pos jest już tylko inkrementowana.

Sama funkcja wewnętrzna printNextChar wywoływana jest raz na końcu funkcji typewriter, a potem sama wielokrotnie (w razie potrzeby) wywołuje siebie z opóźnieniem, używając setTimeout. Za każdym razem wypisuje co najwyżej 1 znak.

metal_man napisał(a)

No i faktycznie, kodu znacznie wiecej niz na poczatku napisalem oraz wiecej niz zaczalem ukladac

To jest niekoniecznie dobrym, ale świadomym rozwiązaniem. Sporo kodu poświęconego jest na walidację przekazanych parametrów. No i te 10 linijek na samo dodanie jednego znaku do tekstu. Można było zrobić container.innerHTML += text.charAt(pos);. I tyle. Ale jeśli w container byłyby jakieś inne elementy, to to zniszczyłoby wszystkie referencje do nich. Usunęłoby też wszystkie funkcje obsługujące zdarzenia na tych elementach.

A czemu wyszło z tego 10 linijek? Bo jest kilka przypadków:

  1. Bieżąca litera to znak nowej linii (\n), więc dodajemy do containera element
  2. Bieżąca litera jest inna oraz:
    2a) ...poprzednio wstawionym węzłem jest węzeł tekstowy -- wtedy dodajemy do tego węzła nową literę
    2b) ...poprzednio wstawionym węzłem jest element (np. element
    z pierwszego punktu). Wtedy tworzymy i dodajemy węzeł tekstowy z nową literą.

Także może trochę dmuchałem na zimne używając bezpiecznych funkcji DOM. A DOM ma to do siebie, że jest naprawdę mało zwięzły.

No i jeszcze dochodzi to, że funkcja ma parę bajerów, których raczej nie planowałeś, ale uznałem je za sensowne. Np. wyświetla tekst odrobinę nienaturalnie, losowo zmniejszając i zwiększając interwały pomiędzy literami (można to wyłączyć). Funkcja działa asynchronicznie, czyli NIE jest tak, że jak napiszesz:

typewriter('element', 'Tekst');
a();

To funkcja a() wykona się po wypisaniu tekstu na ekran. Wykona się natychmiast, a tekst będzie się wyświetlał w tle. Niestety (?) taki asynchroniczny model jest w JavaScripcie "jedyny słuszny". Model synchroniczny (taki, który nie wychodziłby z funkcji typewriter dopóki nie skończyłaby wypisywać tekst) blokowałby cały interfejs użytkownika/całą przeglądarkę na czas wyświetlania tekstu.

Więc uznałem za stosowne, żebyś mógł podać funkcji w czwartym parametrze funkcję, która ma być wykonana po wypisaniu tekstu. Czyli powyższy kod zapisałbyś tak (dodałem w 3 parametrze pusty obiekt z opcjami):

typewriter('element', 'Tekst', {}, a);

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