Anatoly Vorobey (avva) wrote,
Anatoly Vorobey
avva

Category:

минималистский джаваскрипт

(эта запись будет интересна в основном веб-программистам)

Вот это очень круто по-моему - эффект достигнут при помощи менее чем 256 байтов джаваскрипта. Вот этот код:


p01<i id=d><script>setInterval('for(x=_="in solid #",E=Math,o=99,--O;o--;x+=
"<hr style=\'width:0;margin:auto;border-right:"+E.abs(q?p:P)+_+(9-q)*36+
";border-left:"+E.abs(q?P:p)+_+(8+q)*36+"\'>")q=0>(p=E.cos(O+=22))*(P=E.sin(O));
d.innerHTML=x',O=9)</script>


Давайте разберемся.



1. Расставим немного пробелов и переносов строк.


p01 
<i id=d> 
<script>   
  setInterval(' \       
    for(x = _ = "in solid #", E=Math, o=99, --O; \           
      o--; \            
      x += "<hr style=\'width:0;margin:auto;border-right:" \             
        + E.abs(q ? p : P) + _ + (9-q)*36 \
        + ";border-left:" \             
        + E.abs(q ? P : p) 
        + _ 
        + (8+q)*36 + "\'>" \           
       ) \
      q = 0 > (p = E.cos(O+=22))*(P = E.sin(O)); \
    d.innerHTML=x',O=9)
</script>


Что из этого можно понять? У нас есть элемент <i>, которому дано имя 'd'. Вызов setInterval приводит к тому, что первый аргумент, который заканчивается "d.innerHTML=x", т.е. меняет тело элемента на x, будет вызываться каждые 9 миллисекунд (одновременно переменная O инициализируется в 9). Кроме этого присвоения, все, что происходит, это некий цикл, который исполняется 99 раз (начиная с o=99, условие прекращения o-- выполнится, когда o==0), и от цикла к циклу переменная O уменьшается на 1, но кроме того внутри цикла она 99 раз увеличивается на 22: p = E.cos(O+=22).

2. Переименуем некоторые переменные. Вместо E=Math можно везде писать просто Math. Переменная _ всегда имеет значение строки "in solid #". Раскроем некоторые инициализации отдельно. Сделаем структуру цикла более очевидной, накопление внутри переменной x перенесем в тело цикла. Переименуем o в line, т.к. логично предположить, что 99 раз - это 99 строк, а O в angle, т.к. к нему применяют синус и косинус. Наконец, вместо того, чтобы передавать setInterval одну длинную строку, вынесем этот код в функцию и передадим ее.


p01
<i id=d>
<script>
  angle = 9;
  function one_event() {
    x = "in solid #";
    --angle;
    for(line = 99; line != 0; line--) {
      q = 0 > (p = Math.cos(angle += 22))*(P = Math.sin(angle));
      line_html = "<hr style=\'width:0;margin:auto;border-right:"
           + Math.abs(q ? p : P) + "in solid #" + (9-q)*36
           + ";border-left:" + Math.abs(q ? P : p)
           + "in solid #" + (8+q)*36 + "\'>";
      x += line_html;
    }
    d.innerHTML=x;
  }
  setInterval(one_event, 9);
</script>


Читаем дальше. Начальные значения x и angle не играют роли: они нужны были только для того, чтобы определить эти переменные как можно более компактно. Становится понятен смысл "in solid #": in - это дюймы, сразу перед ними идет число - абсолютное значение либо косинуса, либо синуса угла angle - p или P. # предваряет номер цвета, который равен либо 9*36, либо 8*36, т.к. q равен либо 0, либо 1. Когда q равно 1? Когда косинус и синус угла angle одного знака, т.е. угол находится либо в первом квадранте (от 0 до 90 градусов), либо в третьем (от 180 до 270 градусов). (9-q)*36 и (8+q)*36 выбирают всегда разные цвета из набора двух цветов, в зависимости от q.

3. Переименуем переменные и упростим логику кода. Он стал длиннее, но понятнее:


p01
<i id=d>
<script>
  angle = 0;
  function one_event() {
    x = "";
    angle-=1;
    color1 = "#" + 8*36;
    color2 = "#" + 9*36;
    for(line = 0; line != 99; line++) {
      angle += 22;
      cos = Math.cos(angle);
      sin = Math.sin(angle);
      is_quadrant24 = (cos * sin > 0 ? 1 : 0);
      line_html = "<hr style=\'width:0;margin:auto" +

        " ;border-right:"
        + Math.abs(is_quadrant24 ? cos : sin) + "in"
        + " solid "
        + (is_quadrant24 ? color1 : color2)

        + ";border-left:"
        + Math.abs(is_quadrant24 ? sin : cos) + "in"
        + " solid "
        + (is_quadrant24 ? color2 : color1)

        + "\'>";
      x += line_html;
    }
    d.innerHTML=x;
  }
  setInterval(one_event, 9);
</script>


Как рисуется каждая отдельная линия, понятно: с помощью тага hr, который на самом деле ничего не рисует (width:0), но слева и справа у него есть границы разных цветов и протяжений. У каждой отдельной линии длина левой и правой границы равна соответственно синусу и косинусу угла angle, или наоборот косинусу и синусу, в зависимости от того, в какой квадрант попадает angle (is_quadrant24). Цвета левой и правой границ тоже меняются в зависимости от этого, так что цвет color1 всегда соответствует косинусу угла по длине, а цвет color2 - синусу угла по длине.

Осталось разобраться, как каждая линия соотносится с следующей, и почему происходит эффект кручения.

Почему angle+=22, откуда это число? Тут используется тот факт, что 22/7 - очень хорошее приближение числа пи. Соответственно добавить к углу 22 - все равно, что добавить 7*pi, и еще чуть-чуть (примерно 0.009). Добавление 7*pi не меняет абсолютного значения косинуса и синуса угла, а только их знак на обратный - но меняет на обратный знак и того, и другого, поэтому значение is_quadrant24 от этого остается неизменным. По значениям косинуса и синуса angle при каждом прохождении сквозь цикл отличается от предыдущего очень незначительно: угол увеличивается на 0.008, и либо косинус чуть-чуть увеличивается, а синус чуть-чуть уменьшается, либо наоборот. Так и выходит, что граница между двумя цветами ползет из одной стороны в другую. Общая ширина каждой линии равна сумме синуса и косинуса (точнее, их абсолютных значений); минимум этого значения достигается в углах, кратных pi/2, и равен 1 (одному дюйму, благодаря "in"), максимум - в углах, кратных 45 градусов, и равен квадратному корню из 2 - примерно 1.41. Из-за этого возникает впечатление крутящейся ленты, видимая ширина которой колеблется из-за того, что разные ее части выходят на передний план.

За полный проход цикла сдвиг угол увеличивается на 22*99, но если учитывать только сдвиги по отношению к pi, то их накапливается всего лишь около 0.9 (сто раз по 0.009). В начале следующего цикла команда angle-=1 "отменяет" накопившийся сдвиг, еще и добавляя 0.1 по сравнению с тем, с чего начали предыдущий цикл. В итоге выходит, что по значению угла (опять-таки по модулю pi) следующий цикл немного сдвинут, на 0.1 примерно, относительно предыдущего. И это тоже помогает создать впечатление кручения: граница между цветами ползет не только внутри одного пробега из 99 линий, но и между пробегами все время двигается с определенной скоростью.

Угол angle изменяется примерно на 0.1 (по модулю pi) за один вызов функции one_event(). В какой-то момент он пересекает границу pi/2 - границу в 90, 180, 270 или 360 градусов - т.е. переходит в следующий квадрант. В этот момент значение is_quadrant24 меняется с 0 на 1 или наоборот, и соответственно длины границ и их цвета мгновенно меняются местами. Визуально это воспринимается как закручивание, переход от одного цвета на "переднем" плане к другому. Как часто это происходит? Функция вызывается каждые 9 миллисекунд, и нужно примерно 16 вызовов, чтобы накопить, по 0.1 за вызов, разницу в примерно 1.6 (pi/2). Значит, закручивание должно происходить примерно каждые 144 миллисекунды, т.е. около 7 раз в секунду.

Кажется, все; если что-то непонятно, можно спросить.
Subscribe
  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 40 comments
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →
Previous
← Ctrl ← Alt
Next
Ctrl → Alt →