7. Программируем графику. JavaScript (от intuit.ru)

7. Программируем графику. JavaScript (от intuit.ru)

Создание и раскрутка сайтов

Вбиваю в ТОП-10, как гвозди

8-965-148-98-02, diplom07@mail.ru

Создание и раскрутка сайтов

Вбиваю в ТОП-10, как гвозди

8-965-148-98-02, diplom07@mail.ru

7. Программируем графику. JavaScript (от intuit.ru)

7. Программируем графику. JavaScript (от intuit.ru)

www.homa.xp3.biz 

Подробно рассказано о приемах программирования изменений графических образов на HTML-страницах, в частности, JavaScript-мультипликации и графических меню.

Объект Image

Наиболее зрелищные эффекты при программировании на JavaScript достигаются при работе с графикой. При этом в арсенале программиста не так уж много инструментов: встроенные в документ картинки, возможность генерации объекта Image, комбинирование картинок с гипертекстовыми ссылками и таблицами. Тем не менее обилие различных эффектов, которые достигаются этими нехитрыми средствами, впечатляет.

Программирование графики в JavaScript опирается на объект Image, который характеризуется следующими свойствами, методами и событиями:
Характеристики объекта Image
СвойстваМетодыСобытия
name
src
lowSrc
border
height
width
hspace
vspace
complete
нет
Abort
Error
Load

Несмотря на такое обилие свойств, их абсолютное большинство можно только читать, но не изменять. Об этом свидетельствует, прежде всего, отсутствие методов. Но 2 свойства все же можно изменять: src и lowSrc. Этого оказывается достаточно для множества эффектов с картинками.

Все объекты класса Image можно разделить на встроенные и порожденные программистом. Встроенные объекты - это картинки контейнеров IMG. Если эти картинки поименовать, к ним можно обращаться по имени. Например, если у нас имеется картинка (будем считать, что она 1-я в документе):

<IMG NAME=picname SRC=1.gif>

то значение свойства document.images[0].name будет равно "picname", а к самой картинке можно будет обращаться тремя способами:

document.images[0]
document.picname
document.images['picname']

Свойства src и lowSrc

Свойства src и lowSrc определяют URL изображения, которое монтируется внутрь документа. При этом lowSrc определяет временное изображение, обычно маленькое, которое отображается, пока загружается основное изображение, чей URL указывается в атрибуте SRC контейнера IMG. Свойство src принимает значение атрибута SRC контейнера IMG. Программист может изменять значения и src, и lowSrc. В предыдущем примере мы можем изменить значение src следующим образом:

document.picname.src='river.gif';

Как видно из этого примера, существует возможность модифицировать картинку за счет изменения значения свойства src встроенного объекта Image. Если вы в 1-й раз просматриваете данную страницу (т.е. картинки не закешированы браузером), то постепенное изменение картинки будет заметно (конечно, при низкой скорости подключения к Internet; а также это зависит от браузера, который может загрузить картинку, а только потом вывести ее целиком на страницу). Как ускорить это изменение, мы рассмотрим в следующем разделе.

Изменение картинки

Изменить картинку можно, только присвоив свойству src встроенного объекта Image новое значение. Выше было показано, как это делается в простейшем случае. Очевидно, что медленная перезагрузка картинки с сервера не позволяет реализовать быстрое листание. Попробуем решить эту проблему (последующее в этом параграфе писалось в 2001-02гг., когда время подгрузки графики было значительным, чтобы можно было наблюдать описываемые ниже эффекты от оптимизации).

Пример 7.1 (кликните, чтобы открыть в новом окне; для просмотра HTML-кода кликните правой кнопкой мыши и выберите соответствующий пункт)

Решение заключается в разведении по времени подкачки картинки и ее отображения. Для начала мы создаем изображения, к которым привязываем обработчики событий onMouseOver и onMouseOut. При наведении указателя мыши на каждую из картинок она заменяется другой (цветной), а при уводе мышки картинка заменяется обратно на черно-белую:

<IMG NAME=m0 src=i/0.gif onMouseOver="document.0.src=color[0].src"
onMouseOut="document.0.src=mono[0].src">

Более того, если навести мышку на пункт меню справа от карты, то вызовется функция, которая заменяет адреса сразу у нескольких картинок.

Однако, главное не в том, что картинки замещаются, а в том, с какой скоростью они это делают. Для достижения нужного результата в начале страницы создаются массивы картинок, в которые перед отображением перекачивается вся графика. Для этой цели используют конструктор объекта Image:

mono=new Array(32);
for(i=0;i<32;i++)
{mono[i]=new Image();
s=(i<10)?"0":"";
mono[i].src="i/0"+s+i+".gif"}

Именно в тот момент, когда свойству, например, mono[25].src присвавается значение i/5.gif, и происходит скачивание этой картинки с сайта на компьютер пользователя. Если бы вместо объектов класса Image мы составили массив из строк вида i/0.gif и т.д., то никакой подгрузки графики не произошло бы, и каждый раз, когда пользователь наводил бы на очередное изображение, браузеру приходилось бы скачивать новую картинку. Мы поместили этот скрипт в контейнер HEAD, тем самым гарантировав, что к моменту, когда пользователь начнет работать со страницей, все требуемые для работы картинки уже будут скачаны, и в процессе вождения мышки по картинкам никакой задержки показа очередного изображения наблюдаться не будет.

Попутно обратим внимание на следующий трюк, использованный в скрипте. Предположим, нам необходимо написать 32 строчки:

document.m0.src=mono[0].src;
document.m1.src=mono[1].src;
…
document.m31.src=mono[31].src;

Мы хотим избежать написания такого громоздкого кода. С правой частью операторов присваивания справиться легко: достаточно задать цикл по i от 0 до 31 и писать mono[i].src. А вот с левой частью не все так просто. На помощь приходит функция eval("выражение"), которая воспринимает переданное ей выражение как программу JavaScript и выполняет все содержащиеся в ней операторы. С ее помощью решить нашу проблему легко - мы составляем нужную строчку, а затем отдаем ее на выполнение:

for(i=0;i<32;i++)eval("document.m"+i+".src=mono["+i+"].src;")

Мультипликация

Естественным продолжением идеи замещения значения атрибута SRC в контейнере IMG является мультипликация, т.е. последовательное изменение значения этого атрибута во времени. Для реализации мультипликации используют метод setTimeout() объекта window.

Существует 2 способа запуска мультипликации: по окончании загрузки страницы (onLoad) и при действиях пользователя (onClick, onChange и т.д.). Наиболее популярный - 1-й, а именно использование onLoad() и setTimeout().

Обработчик события onLoad

Событие Load наступает в момент окончания загрузки документа браузером. Обработчик данного события (onLoad) указывается в контейнере BODY:

<BODY onLoad="программа JavaScript">

Рассмотрим сначала пример, в котором при загрузке документа начинает выполняться бесконечный цикл изменения картинки:

<SCRIPT>
i=0;function movie()
{document.i.src='images/crou00'+i+'.gif';i++;if(i>4)i=0;setTimeout('movie();',500)}
</SCRIPT>
<BODY onLoad=movie()>
<IMG NAME=i>

Пример 7.1. Бесконечная мультипликация

Можно реализовать и конечное число циклов мультипликации, скажем 5:

<SCRIPT>
i=0,n=5;//число циклов
function movie()
{document.i.src='images/crou00'+i+'.gif';i++;if(i>4){i=0;n--;}
if(n>0)setTimeout('movie();',500)}
</SCRIPT>
<BODY onLoad=movie()>
<IMG NAME=i>

Пример 7.2. Мультипликация с числом циклов n=5

В обоих примерах следует обратить внимание на использование метода setTimeout(). На 1-й взгляд, это просто рекурсия. Но в действительности все несколько сложнее. JavaScript разрабатывался для многопоточных операционных систем, поэтому правильнее будет представлять себе исполнение скриптов следующим образом:

  • скрипт movie() получает управление от обработчика события onLoad;
  • заменяет картинку;
  • порождает новый скрипт movie() и откладывает его исполнение на 500мс;
  • текущий скрипт movie() уничтожается JavaScript-интерпретатором.

    После окончания срока задержки исполнения все повторяется. В первом примере (бесконечное повторение) функция порождает саму себя и, тем самым, поддерживает непрерывность своего выполнения. Во втором примере (конечное число итераций) после нескольких повторов функция не порождается. Это приводит к завершению процесса отображения новых картинок.

    Запуск и остановка мультипликации

    Постоянная мультипликация может быть достигнута и другими средствами, например многокадровыми графическими файлами. Однако движение на странице - не всегда благо. Часто возникает желание реализовать запуск и остановить движения по требованию пользователя. Удовлетворим это желание, модифицировав пример с бесконечной мультипликацией:

    <SCRIPT>
    var i=0,flag=true;
    function movie()
    {if(flag)
    {document.i.src='images/crou00'+i+'.gif';i++;if(i>4)i=0}
    setTimeout('movie();',500)}
    </SCRIPT>
    <BODY onLoad=movie()>
    <FORM>
    <INPUT TYPE=button VALUE="Start/Stop" onClick="flag=!flag">
    </FORM>
    <IMG NAME=i>
    

    Пример 7.3. Остановка/запуск мультипликации (поток генерируется постоянно)

    Мы ввели булевский flag и меняем изображение на странице, только если он принимает значение true. Нажатие на кнопку меняет значение флага на его отрицание.

    Обратите внимание: когда flag имеет значение false, мы просто обходим изменение картинки, но не прекращаем порождение потока. Если бы мы поместили setTimeout() внутрь конструкции if(), то после нажатия на кнопку поток перестал бы порождаться, и запустить мультипликацию заново стало бы нельзя. Однако постоянное генерирование потока - это некоторая растрата ресурсов (памяти, процессора). Нельзя ли сделать оптимальнее? Можно. Этот способ основан на применении метода clearTimeout():

    <SCRIPT>
    var i=0,flag=true,m=null;
    function movie()
    {if(flag)
    {document.i.src='images/crou00'+i+'.gif';i++;if(i>4)i=0}
    m=setTimeout('movie();',500)}
    </SCRIPT>
    <BODY onLoad=movie()>
    <IMG NAME=i>
    <FORM>
    <INPUT TYPE=button VALUE="Start/Stop" onClick="flag=!flag;
    if(flag)movie();
    else clearTimeout(m)">
    </FORM>
    

    Пример 7.4. Остановка/запуск мультипликации (поток приостанавливается)

    Как видите, достаточно ввести идентификатор потока m и сохранять в нем ссылку на поток при вызове setTimeout(). Тогда в случае необходимости (при нажатии пользователем кнопки) мы можем отменить запланированное выполнение movie() (которое произошло бы через 500мс), вызвав метод clearTimeout(m).

    Оптимизация отображения

    При программировании графики следует учитывать множество факторов, которые влияют на скорость отображения страницы и скорость изменения графических образов. При этом обычная дилемма оптимизации программ - скорость или размер занимаемой памяти - решается только в пользу увеличения скорости. О размере памяти при программировании на JavaScript думать как-то не принято.

    Из всех способов оптимизации отображения картинок мы остановимся только на нескольких:

  • оптимизация отображения при загрузке;
  • оптимизация отображения за счет предварительной загрузки;
  • оптимизация отображения за счет нарезки изображения.

    Если 1-е 2 позиции относятся в равной степени как к отображению статических картинок, так и к мультипликации, то 3-й пункт характерен главным образом для мультипликации.

    Оптимизация при загрузке изображений

    Практически в любом руководстве по разработке HTML-страниц отмечается, что при использовании контейнера IMG в теле HTML-страницы следует указывать атрибуты WIDth и HEIGHT. Это продиктовано порядком загрузки компонентов страницы с сервера и алгоритмом работы HTML-парсера. Первым загружается текст разметки. После этого парсер разбирает текст и начинает загрузку дополнительных компонентов, в том числе графики. При этом загрузка картинок, в зависимости от типа HTTP-протокола, может идти последовательно или параллельно.

    Также параллельно с загрузкой парсер продолжает свою работу. Если для картинок заданы параметры ширины и высоты, то можно отформатировать текст и отобразить его в окне браузера. До тех пор, пока эти параметры не определены, отображения текста не происходит. (В современных браузерах текст обычно отображается сразу, оставляя картинке "небольшое" место; по окончании загрузки картинки (или когда становятся известными ее размеры) страница переформатируется "на лету").

    Таким образом указание высоты и ширины картинки позволит отобразить документ раньше, чем картинки будут получены с сервера. Это дает пользователю возможность читать документ или задействовать его гипертекстовые ссылки до момента полной загрузки документа.

    С точки зрения JavaScript, указание размеров картинки задает начальные параметры окна отображения графики внутри документа. Это позволяет воспользоваться маленьким прозрачным образом, для того, чтобы заменить его полноценной картинкой. Идея состоит в передаче маленького объекта для замещения его по требованию большим объектом.

    Предварительная загрузка изображений

    Замена одного образа другим часто бывает оправдана только в том случае, когда это происходит достаточно быстро. Если перезагрузка длится долго, то эффект теряется. Для быстрой подмены используют возможность предварительной загрузки документа в специально созданный объект класса Image.

    Реальный эффект можно почувствовать только при отключении кэширования страниц на стороне клиента (браузера). Кэширование часто используют для ускорения работы со страницами Web-узла. Как правило, загрузка первой страницы - это достаточно длительный процесс. Самое главное, чтобы пользователь в этот момент был готов немного подождать. Поэтому, кроме графики, необходимой только на первой странице, ему можно передать и графику, которая на ней не отображается. Но зато при переходе к другим страницам узла она будет отображаться без задержки на передачу с сервера.

    Описанный выше прием неоднозначен. Его оправдывает только то, что если пользователь нетерпелив, то он вообще отключит передачу графики.

    Нарезка изображений

    Нарезка картинок применяется довольно часто. Она позволяет достигать эффекта частичного изменения отображаемой картинки. Чаще всего он применяется при создании меню.

    Кроме подобного эффекта нарезка позволяет реализовать мультипликацию на больших картинках. При этом изменяется не весь образ, а только отдельные его части.

    Графика и таблицы

    Одним из наиболее популярных приемов дизайна страниц Web-узла является техника нарезки картинок на составные части. Можно выделить следующие способы применения этой техники для организации навигационных компонентов страницы:

  • горизонтальные и вертикальные меню;
  • вложенные меню;
  • навигационные графические блоки.

    Горизонтальное меню

    Главной проблемой при использовании нарезанной графики является защита ее от контекстного форматирования страницы HTML-парсером. Дело в том, что он автоматически переносит элементы разметки на новую строку, если они не помещаются в одной. Составные части нарезанной картинки должны быть расположены на экране определенным образом, но простое их перечисление в ряд не дает желаемого эффекта.

    Пример 7.2. Откройте данный пример в новом окне и попробуйте уменьшить ширину окна, так чтобы она стала меньше общей ширины всех картинок. Вы увидите, что картинки начинают переноситься на новую строку, когда ширина раздела становится меньше общей ширины всех картинок:

    <IMG SRC=horis1.gif><IMG SRC=horis2.gif><IMG SRC=horis3.gif><IMG SRC=horis4.gif>

    Пример 7.5. Горизонтальное меню (картинки съезжают в узком окне)

    Рис.7.1. Горизонтальное меню (картинки съезжают в узком окне)

    Пример 7.3. Проблема решается применением защиты от переноса на следующую строчку - контейнера <pre>:

    <IMG SRC=horis1.gif><IMG SRC=horis2.gif><IMG SRC=horis3.gif><IMG SRC=horis4.gif>

    Пример 7.6. Горизонтальное меню (картинки защищены от переноса)

    Рис.7.2. Горизонтальное меню (картинки защищены от переноса)

    Пример 7.4. Если мы хотим использовать эту последовательность картинок как меню, то нужно задать гипертекстовые ссылки, что приводит к появлению рамок вокруг изображений. Это происходит потому, что по умолчанию толщина границ у изображений, являющихся ссылками - ненулевая.

    <A HREF=courses.htm><IMG SRC=horis1.gif></A><A HREF=setting.htm><IMG SRC=horis2.gif></A><A HREF=baskets.htm><IMG SRC=horis3.gif></A><A HREF=thehelp.htm><IMG SRC=horis4.gif></A>

    Пример 7.7. Горизонтальное меню (рамки вокруг картинок)

    Рис.7.3. Горизонтальное меню (рамки вокруг картинок)

    Пример 7.5. Устранить этот недостаток можно путем задания значения атрибута у изображений:

    <A HREF=courses.htm><IMG SRC=horis1.gif></A><A HREF=setting.htm><IMG SRC=horis2.gif></A><A HREF=baskets.htm><IMG SRC=horis3.gif></A><A HREF=thehelp.htm><IMG SRC=horis4.gif></A>

    Пример 7.8. Горизонтальное меню (рамки более не видны)

    Рис.7.4. Горизонтальное меню (рамки более не видны)

    Вертикальное меню

    Пример 7.6. Теперь попробуем тем же способом реализовать вертикальное меню. При просмотре в некоторых браузерах сплошной вертикальной линии не получается, т.к. высота строки не равна высоте картинки. Подогнать эти параметры практически невозможно. Каждый пользователь настраивает браузер по своему вкусу.

    <IMG SRC=vert.gif><A HREF=courses.htm><IMG SRC=vert1.gif></A>
    <IMG SRC=vert.gif><A HREF=setting.htm><IMG SRC=vert2.gif></A>
    <!--далее аналогично для vert3.gif и vert4.gif-->
    

    Пример 7.9. Вертикальное меню (линия не сплошная)

    Рис.7.5. Вертикальное меню (линия не сплошная)

    Пример 7.7. Решение заключается в использовании (вместо контейнера pre) таблицы table с нулевыми границами между ячейками:

    <table id=t>
    <tr><td><IMG SRC=vert.gif>
    <td><A HREF=courses.htm><IMG SRC=vert1.gif></A>
    <!--далее аналогично для vert2, vert3, vert4.gif-->
    </table>
    

    Пример 7.10. Вертикальное меню (линия сплошная)

    Рис.7.6. Вертикальное меню (линия сплошная)

    В данном случае все картинки удается сшить без пропусков и тем самым достичь непрерывности навигационного дерева. Пропуски устраняются путем применения атрибутов BORDER, CELLSPACING и CELLPADDING. 1-й устраняет границы между ячейками и вокруг всей таблицы, 2-й устанавливает расстояние между ячейками равным 0 пикселов, 3-й устанавливает отступ между границей ячейки и элементом, помещенным в нее, в 0 пикселов.

    Выделение выбранного пункта меню

    Практически все, что было изложено до сих пор, касается вопросов построения одноуровневых меню. Поэтому в данном разделе мы постараемся привести более или менее реальные примеры таких меню. Графическое меню удобно тем, что автор может всегда достаточно точно расположить его компоненты на экране. Это, в свою очередь, позволяет и другие элементы страницы точнее располагать относительно элементов меню.

    Пример 7.8. В данном примере мы воспользуемся этим для того, чтобы выделить тот пункт меню, над которым находится указатель мыши. Поскольку пункты меню прилегают друг к другу без просветов, то мы будем указывать стрелочкой тот пункт меню, который активен в данный момент.

    <table id=t>
    <tr><td><IMG NAME=e1 SRC=empty.gif WIDth=15 HEIGHT=8>
    <!--аналогично для e2, e3, e4-->
    <tr><td><A HREF=javascript:void(0) onMouseover="document.e1.src='arrowdw.gif'"
    onMouseout="document.e1.src='empty.gif'">
    <IMG SRC="horis1.gif" BORDER="0"></A>
    <!--аналогично для e2, e3, e4-->
    </table>
    

    Пример 7.11. Горизонтальное меню (пункт указан стрелкой)

    Рис.7.7. Горизонтальное меню (пункт указан стрелкой)

    Стрелочка бежит точно над тем элементом, на который указывает мышь.

    Пример 7.9. Однако стоит заметить, что применение атрибута ALT у контейнера IMG и его дублирование в строке статуса является гораздо более информативным, чем добавление нового графического элемента. Правда, отображается содержание ALT с некоторой задержкой:

    <A HREF=courses.htm onMouseOut=window.status='' onMouseOver=window.status='Мои курсы';return true><IMG SRC=horis1.gif></A><A HREF=setting.htm onMouseOut=window.status='' onMouseOver=window.status='Настройки';return true><IMG SRC=horis2.gif></A><A HREF=baskets.htm onMouseOut=window.status='' onMouseOver="window.status='Корзина';return true"><IMG SRC=horis3.gif"></A><A HREF=thehelp.htm onMouseOut="window.status=''" onMouseOver="window.status='Помощь';return true"><IMG SRC=horis4.gif></A>
    

    Пример 7.12. Горизонтальное меню (атрибут ALT и поле статуса)

    Рис.7.8. Горизонтальное меню (атрибут ALT и поле статуса)

    Пример 7.10. Посмотрим теперь на реализацию вертикального меню, построенного на основе графических блоков текста:

    <table id=t>
    <SCRIPT>
    for(i=1;i<5;i++)
    {var ileft='<IMG SRC=block'+i+'.gif>',
    iright='<IMG NAME=e'+i+' SRC=clear.gif>',
    isrc='document.e'+i+'.src',
    alink='<A HREF="javascript:void(0);" '+
    'onMouseover="'+isrc+'=\'corner.gif\';" '+
    'onMouseout="'+isrc+'=\'clear.gif\';">';
    document.write('<TR>'+
    '<td>'+alink+ileft+'</A>'+
    '<td>'+alink+iright+'</A>'+
    '')}
    </SCRIPT>
    </table>
    

    Пример 7.13. Вертикальное меню (графические блоки текста)

    Здесь продемонстрировано типичное применение JavaScript - многократное генерирование похожих фрагментов HTML-кода с помощью цикла и метода document.write(). В данном примере с помощью цикла мы генерируем HTML-фрагмент следующего вида (каждый раз с разным номером вместо 1):

    <tr><td><A onMouseover=document.e1.src='corner.gif' onMouseout=document.e1.src='clear.gif' HREF=javascript:void(0)><IMG SRC=block1.gif></A>
    <td><A onMouseover="document.e1.src='corner.gif'"
    onMouseout=document.e1.src='clear.gif' HREF=javascript:void(0)><IMG NAME=e1 SRC=clear.gif></A>
    

    При движении мыши у соответствующего пункта меню, попавшего в фокус мыши, "отгибается уголок". В данном случае "уголок" - это самостоятельная картинка. Все уголки реализованы в правой колонке таблицы. Для того чтобы гипертекстовая ссылка срабатывала по обеим картинкам (тексту и "уголку"), применяются одинаковые контейнеры A, охватывающие каждую картинку.

    Рис.7.9. Вертикальное меню (графические блоки текста)

    Вложенные меню

    В HTML нет стандартного способа реализации вложенных меню. Тем не менее за счет графики можно создать их подобие. При этом следует понимать, что место, на которое ложится графика, нельзя заполнить текстом.

    Пример 7.11. В этом примере вложенное меню расположено справа от основного. Эффект вложенности достигается за счет изменения цвета (подпункты имеют цвет, отличный от цвета пунктов).

    <SCRIPT>
    function submenu(a)
    {with(document)
    {if(a==1)
    {item1.src="item_1_yes.gif";//1-й пункт активен
    item2.src="item_2_no.gif";//2-й пункт неактивен
    subi1.src="item_1_1.gif";//1-й пункт вложенного меню 1
    subi2.src="item_1_2.gif";//2-й пункт вложенного меню 1
    }
    if(a==2)
    {item1.src="item_1_no.gif";//2-й пункт активен
    item2.src="item_2_yes.gif";//1-й пункт неактивен
    subi1.src="item_2_1.gif";//1-й пункт вложенного меню 2
    subi2.src="item_2_2.gif";//2-й пункт вложенного меню 2
    }}}
    </SCRIPT>
    <table id=t>
    <tr><td><A HREF=javascript:void(0) onMouseOver=submenu(1)>
    <IMG NAME=item1 SRC=item_1_yes.gif></A>
    <td><IMG NAME=subi1 SRC=item_1_1.gif><tr><td><A HREF=javascript:void(0) onMouseOver=submenu(2)>
    <IMG NAME=item2 SRC=item_2_no.gif></A>
    <td><IMG NAME=subi2 SRC=item_1_2.gif></table>
    

    Пример 7.14. Вложенное меню (пункт выделен цветом)

    Рис.7.10. Вложенное меню (пункт выделен цветом)

    Подчиненность меню можно подчеркнуть изменением его положения относительно основного меню (составьте соответствующий код самостоятельно):

    Рис.7.11. Вложенное меню (пункт выделен сдвигом)

    В этом случае для продвижения меню вниз необходимо зарезервировать место при помощи невидимых или видимых картинок.

    При использовании слоев можно создать настоящее выпадающее меню.

  • Отвечу на любые вопросы. С уважением, Дмитрий Владимирович:
    8-965-148-98-02, diplom07@mail.ru.
    Печатать страницу: 7. Программируем графику. JavaScript (от intuit.ru)

    7, Программируем графику, JavaScript (от intuit,ru), 7, Программируем, графику, JavaScript, (от, intuit,ru), создание, сайта, веб, заказ, web, дизайн, раскрутка, продвижение, новый, сайт, рейтинг, сео, seo, Создание сайтов, web-программирование, seo-продвижение, создание и раскрутка сайтов, вбиваю в топ-10, как гвозди, попробуйте бесплатно, Создание сайтов, web-программирование, seo-продвижение, 7, Программируем, графику, JavaScript, (от, intuit,ru), создание, сайта, веб, заказ, web, дизайн, раскрутка, продвижение, новый, сайт, рейтинг, сео, seo, создание, сайта, веб, заказ, web, дизайн, раскрутка, продвижение, новый, сайт, рейтинг, сео, seo, раскрутка сайтов, Вбиваю в ТОП-10, как гвозди, раскрутка сайтов, Делаю и раскручиваю сайты, Дмитрий Владимирович

    ×

    Ваше письмо