• Canvas как способ оптимизации графики

    Мы постепенно начинаем обновлять дизайн сайта Аймобилко и уже выкатили пару новых макетов. Самое заметное изменение — это главная страница сайта:

    ss01

    Дизайнер очень хорошо постарался: страница выглядит очень красиво и современно. Осталось только перенести всё эту красоту из фотошопа в веб.

    Центральный элемент страницы — сцена, на которой показываются новинки нашего каталога. На фоне сцены находится очень большая картинка с кулисами. Если присмотреться, то эти кулисы покрыты лёгким шумом, что придаёт им особый колорит:

    example

    Однако вся эта красота на проверку оказалась очень тяжёлой: в одной картинке объединилось всё худшее, что плохо влияет на сжатие. Это и красный цвет (даёт очень сильные артефакты сжатия в JPEG), и мелкий шум (сильные артефакты в JPEG; плохо упаковывается в PNG). Приемлемое качество картинки было достигнуто при размере в 330 КБ, что, на мой взгляд, довольно много для одной картинки. Очень хочется, чтобы главная страница загружалась как можно быстрее. Поэтому я решился на один эксперимент.

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

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

    Простой алгоритм монохромного шума выглядит так:

    var canvas = document.createElement('canvas');
    canvas.width = canvas.height = 200;
    var ctx = canvas.getContext('2d');
    
    // получаем все пиксели изображения
    var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    var pixels = imageData.data;
    
    for (var i = 0, il = pixels.length; i < il; i += 4) {
    	var color = Math.round(Math.random() * 255);
    	// так как шум монохромный, в каналы R, G и B кладём одно и то же значение
    	pixels[i] = pixels[i + 1] = pixels[i + 2] = color;
    
    	// делаем пиксель непрозрачным
    	pixels[i + 3] = 255;
    }
    
    // записываем пиксели обратно на холст
    ctx.putImageData(imageData, 0, 0);
    
    document.body.appendChild(canvas);
    

    Получим вот такой результат:

    noise

    Вполне неплохо для начала. Однако нам не достаточно просто сгенерировать слой с шумной текстурой и наложить его на кулисы с полупрозрачностью. Если ещё внимательней присмотреться к картинке, то можно заменить, что там нет светлых пикселей, есть только тёмные. То есть монохромный шум должен быть наложен на картинку в режиме Multiply.

    Режимы наложения

    Каждый, кто работал с фотошопом и другими продвинутыми графическими редакторами, знает, что такое режимы наложения слоёв:

    blending-modes

    Кому-то они могут показаться запредельно сложными с точки зрения программной реализации, однако на самом деле практически все режимы наложения основаны на очень простых алгоритмах. Например, алгоритм режима наложения Multiply выглядит так:

    (colorA * colorB) / 255

    То есть просто умножаем два цвета и делим результат на 255 (отсюда и название Multiply: «умножение»).

    Доработаем нашу функцию: загрузим картинку, сгенерируем шум и наложим его в режиме Multiply:

    // Загружаем картинку. Обязательно ждём, пока она полностью загрузится
    var img = new Image;
    img.onload = function() {
    	addNoise(img);
    };
    img.src = "stage-bg.jpg";
    
    
    function addNoise(img) {
    	var canvas = document.createElement('canvas');
    	canvas.width = img.width;
    	canvas.height = img.height;
    
    	var ctx = canvas.getContext('2d');
    
    	// нарисуем картинку на холсте, чтобы получить её пиксели
    	ctx.drawImage(img, 0, 0);
    
    	// получаем все пиксели изображения
    	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    	var pixels = imageData.data;
    
    	for (var i = 0, il = pixels.length; i < il; i += 4) {
    		// генерируем пиксель «шума»
    		var color = Math.random() * 255;
    
    		// накладываем пиксель шума в режиме multiply на каждый канал
    		pixels[i] = pixels[i] * color / 255;
    		pixels[i + 1] = pixels[i + 1] * color / 255;
    		pixels[i + 2] = pixels[i + 2] * color / 255;
    	}
    
    	ctx.putImageData(imageData, 0, 0);
    	document.body.appendChild(canvas);
    }
    

    Получим что-то типа этого:

    stage-noise

    Уже похоже на правду, но шум получился грубый: нужно наложить его с меньшей прозрачностью.

    Альфа-композиция

    Процесс смешивание двух цветов с учётом прозрачности называется «альфа-композиция». В простейшем варианте алгоритм смешивания выглядит так:

    colorA * alpha + colorB * (1 - alpha)

    где alpha — это коэффициент смешивания (прозрачность) от 0 до 1. В данном случае важно правильно выбрать, что будет фоновым изображением (colorB), а что будет накладываемым (colorA). В нашем случае фоновой будет сцена, а шум — накладываемым.

    Добавим в функцию addColor() дополнительный параметр alpha и модифицируем сам алгоритм с учётом альфа-композиции:

    var img = new Image;
    img.onload = function() {
    	addNoise(img, 0.4);
    };
    img.src = "stage-bg.jpg";
    
    
    function addNoise(img, alpha) {
    	var canvas = document.createElement('canvas');
    	canvas.width = img.width;
    	canvas.height = img.height;
    
    	var ctx = canvas.getContext('2d');
    	ctx.drawImage(img, 0, 0);
    
    	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    	var pixels = imageData.data, r, g, b;
    
    	for (var i = 0, il = pixels.length; i < il; i += 4) {
    		// генерируем пиксель «шума»
    		var color = Math.random() * 255;
    
    		// высчитываем итоговый цвет в режиме multiply без альфа-композиции
    		r = pixels[i] * color / 255;
    		g = pixels[i + 1] * color / 255;
    		b = pixels[i + 2] * color / 255;
    
    		// альфа-композиция
    		pixels[i] =     r * alpha + pixels[i] * (1 - alpha);
    		pixels[i + 1] = g * alpha + pixels[i + 1] * (1 - alpha);
    		pixels[i + 2] = b * alpha + pixels[i + 2] * (1 - alpha);
    	}
    
    	ctx.putImageData(imageData, 0, 0);
    	document.body.appendChild(canvas);
    }
    

    Получаем именно то, что нам нужно: слой шума, наложенный на картинку в режиме Multiply и прозрачностью 20%:

    stage-noise-alpha

    Оптимизация

    У меня картинка генерируется примерно за 400 мс, что довольно заметно. Поэтому мы оптимизируем код, чтобы он работал быстрее.

    Размер моей картинки 1293×897 пикселей, что в итоге даёт 1 159 821 итераций цикла. Это довольно много, поэтому в первую очередь нужно оптимизировать операции вычисления, а именно убрать ненужные и повторяющиеся операции.

    Например, в цикле три раза высчитывается значение 1 - alpha, хотя это постоянное значение для всей функции, поэтому делаем новую переменную за пределами цикла:

    var alpha1 = 1 - alpha;

    Далее, при генерации пикселя шума используется формула Math.random() * 255, однако дальше мы делим этот цвет на 255: r = pixels[i] * color / 255. Соответственно, умножение и деление на 255 можно смело убирать.

    Эти простые операции снизили время выполнения скрипта с 400 мс до 300 мс (-25%).

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

    var origR = pixels[i],
    	origG = pixels[i + 1],
    	origB = pixels[i + 2];
    

    Это экономит ещё около 40 мс.

    С учётом всех оптимизаций функция addNoise() выглядит вот так:

    function addNoise(img, alpha) {
    	var canvas = document.createElement('canvas');
    	canvas.width = img.width;
    	canvas.height = img.height;
    
    	var ctx = canvas.getContext('2d');
    	ctx.drawImage(img, 0, 0);
    
    	var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    	var pixels = imageData.data, r, g, b, origR, origG, origB;
    	var alpha1 = 1 - alpha
    
    	for (var i = 0, il = pixels.length; i < il; i += 4) {
    		// генерируем пиксель «шума»
    		var color = Math.random();
    
    		origR = pixels[i];
    		origG = pixels[i + 1];
    		origB = pixels[i + 2];
    
    		// высчитываем итоговый цвет в режиме multiply без альфа-композиции
    		r = origR * color;
    		g = origG * color;
    		b = origB * color;
    
    		// альфа-композиция
    		pixels[i] =     r * alpha + origR * alpha1;
    		pixels[i + 1] = g * alpha + origG * alpha1;
    		pixels[i + 2] = b * alpha + origB * alpha1;
    	}
    
    	ctx.putImageData(imageData, 0, 0);
    	document.body.appendChild(canvas);
    }
    

    Скорость выполнения скрипта — около 170 мс (было 400 мс), что довольно неплохо.

    Ещё больше оптимизаций

    Внимательный читатель мог заметить, что картинка с кулисами — красная. То есть информация об изображении присутствует только в красном канале, в синем и зелёном её нет. Если её нет, зачем делать вычисления для этих каналов? Поэтому оставляем расчёты только для красного канала: конкретно в моём случае это даст аналогичный результат, а время выполнения снизит до 80 мс:

    for (var i = 0, il = pixels.length; i < il; i += 4) {
    	origR = pixels[i];
    	pixels[i] = origR * Math.random() * alpha + origR * alpha1;
    }
    

    Добавлено: читатель @Sergeyev указал, что можно ещё сократить время выполнения скрипта (-20%), убрав ненужные операции:

    for (var i = 0, il = pixels.length; i < il; i += 4) {
    	pixels[i] = pixels[i] * (Math.random() * alpha + alpha1);
    }
    

    Результат

    Результат получился довольно неплохим:

    • Вес изображения снизился с 330 КБ до 70 КБ + 1 КБ пожатого JS-кода. На самом деле, картинку можно было бы ещё больше ужать, потому что слой с шумом скроет большинство артефактов JPEG-сжатия.
    • Такая оптимизация соответствует практикам progressive enhancement: пользователи с браузерами, в которых нет canvas (например, IE6) или отключён JS всё равно получат картинку, но менее детализированную.

    Единственный минус, который я вижу — это выполнение наложения каждый раз при загрузке страницы, в то время как обычная картинка может быть просто закэширована браузером. Но, во-первых, время выполнения наложения довольно низкое (80 мс), а во-вторых, как вариант, результат можно хранить в localStorage в виде data:url и при следующей загрузке страницы доставать из кэша. Но моя картинка занимает более 1 МБ, так что я не стал сохранять её — доступное пространство можно и нужно использовать с большей пользой.

    В целом, считаю эксперимент удавшимся, посмотрим, будут ли жалобы со стороны пользователей. Кто хочет попробовать что-то подобное на своих проектах — добро пожаловать в онлайн-демо, в котором я собрал реализации популярных режимов наложения, чтобы оценить их скорость и качество.

    Добавлено: некоторые читатели и Денис в частности справедливо заметил в комментариях, что практически идентичного результата можно добиться наложением слоя с шумом поверх картинки. Тут скорее речь идёт о не совсем удачном примере, выбранном для статьи, нежели о неправильности подхода в целом.

    Метки: , ,
  • 59 комментариев

    1. egorinsk
      11 августа 2011

      Решение, конечно, интересное, но миллион итераций, при каждом переходе по страницам, да на каком-нибудь дохлом девайсе, наверно, будет тормозить. Не было мысли, например, сгенерировать несколько прозрачных небольших канвасов с шумом, и накладывать их на картинку? Не уверен, что drawImage() поддерживает multiply, но может, тут и простого наложения хватит?

      Все же, по моему, решение тяжеловатое.

    2. musius
      11 августа 2011

      В теории, шум можно было сделать по принципу «цикады»: http://habrahabr.ru/blogs/css/117160/

      Не рассматривали такой вариант?

    3. 11 августа 2011

      Очень понравилось онлайн-демо.
      Про него можно было бы отдельную статью написать.

    4. Андрей
      11 августа 2011

      Пробовали переместить шум в отдельную повторяющуюся картинку и наложить ее поверх картинки с кулисами?

    5. 11 августа 2011

      var pixelsLength = pixels.length;

    6. 11 августа 2011

      По поводу красного цвета и артефактов сжатия jpeg — можно передавать цвета лучше. Обычно компрессоры на яркость отводят «вдвое больше информации, чем на цвет». Можно это соотношение и поменять. Хороший пример приведен у Давида Мзареуляна http://david-m.livejournal.com/1010761.html

    7. Сергей Чикуенок
      11 августа 2011

      Решение, конечно, интересное, но миллион итераций, при каждом переходе по страницам, да на каком-нибудь дохлом девайсе, наверно, будет тормозить.

      Не надо боятся таких цифр. Для современных браузеров (а только у них есть canvas) это не проблема. Тем более, я привёл конкретный цифры: 80 мс на обработку картинки (а сейчас это уже 50 мс с учётом дополнительных оптимизаций).

      В теории, шум можно было сделать по принципу “цикады”: http://habrahabr.ru/blogs/css/117160/

      Теория цикад немного для других вещей служит: с помощью неё можно разнообразить повторяющуюся текстуру. Здесь же наложение шума на картинку.

      Очень понравилось онлайн-демо. Про него можно было бы отдельную статью написать.

      Про неё особо нечего писать: все идеи и принципы изложены в этой статье, в демо просто есть выбор способа наложения картинок.

      Пробовали переместить шум в отдельную повторяющуюся картинку и наложить ее поверх картинки с кулисами?

      Это не поможет: у картинки с кулисами разная освещённость в разных областях.

      По поводу красного цвета и артефактов сжатия jpeg — можно передавать цвета лучше.

      Да, спасибо, я буду иметь это в виду. Но в моём случае это не поможет, потому что основной проблемой является шум, который не превращается в кашу только при очень больших значениях качества сжатия.

    8. 11 августа 2011

      У тебя там, кажется, к int приводится в коде на сайте: b >> 0, есть способ короче: b|0. Ну и вообще, код можно ещё ужать 🙂

    9. 11 августа 2011

      И, кстати, спасибо за статью! Как всегда креативно и интересно.

    10. Сергей Чикуенок
      11 августа 2011

      Как оказалось, отдавать int в image data совсем необязательно: браузер сам его приведёт к нужному типу 🙂 Я уже внёс некоторые изменения и ускорил отрисовку, сегодня код на продакшн попадёт.

    11. 11 августа 2011

      Круто! Всегда интересно читать про такие оптимизации, как с точки зрения идеи, так и конкретных примеров в яваскрипте.

      У меня небольшой оффтопик, но про главную страницу: .news-list-title>a { padding:.15em 0; } — и ховер у этих многострочных ссылок не будет «рябить» 🙂

    12. nsinreal
      11 августа 2011

      А почему нельзя шум вынести в отдельную картинку и с помощью css её наложить на хороший сжатый фон?

    13. Александр Карпинский
      11 августа 2011

      В Опере очень медленно выполняется, за 350 мс. Не очень она переваривает огромное кол-во рандомов. Поэтому генерируем строку рандомов заранее, потом только смещение в ней меняем.
      Этот отрисовывает за 140 мс: http://pastie.org/2356062
      В остальных брауерах прирост есть, но не столь значительный. Ну и плюс, рестрограды будут рады (420 мс против 963 мс в третьем ФФ и 780 мс против двух секунд в Опере 10.10).

    14. Александр Карпинский
      11 августа 2011

      При дальнейшей оптимизации (работа только с целыми числами, коих в js как бы нет) — http://pastie.org/2356271 — Опера ускоряется еще в два раза, а остальные начинают замедляться. Если вдруг кому-то придет в голову накладывать шум в реальном времени, есть смысл сделать разные версии для разных браузеров.

    15. Сергей Чикуенок
      11 августа 2011

      А почему нельзя шум вынести в отдельную картинку и с помощью css её наложить на хороший сжатый фон?

      Попробуйте так сделать и посмотрите на результат.

      Этот отрисовывает за 140 мс: http://pastie.org/2356062

      Да, хороший подход. Судя по всему, Опера тормозит на преобразованиях float → int в imageData. Я сделал вот так, вроде везде быстро пашет:

      var rl = (ctx.canvas.width * 3.73) | 0;
      var randoms = new Array(rl);
      
      for (var i = 0; i < rl; i++) {
      	randoms[i] = Math.random() * alpha + alpha1;
      }
      
      for (var i = 0, il = pixels.length; i < il; i += 4) {
      	pixels[i] = (pixels[i] * randoms[i % rl]) | 0;
      }
      
    16. Влад
      11 августа 2011

      Сергей спасибо за статью, я не занимаюсь html а делаю все на flash, но очень полезно было почитать. Спасибо вам, что вы такой смелый и успеваете такие полезные штуки делать.

    17. Александр Карпинский
      11 августа 2011

      Слона-то я не приметил. Действительно, в Опере тормозит именно присваивание в pixels[i] дробных чисел. Только вариант http://pastie.org/2356062 с измененной строкой:
      pixels[i] = pixels[i] * randoms[x + offset] | 0;
      работает у меня быстрее во всех браузерах (я уже тестирую на картинке в 4 раза больше исходной, чтобы заметить результат) чем:
      pixels[i] = (pixels[i] * randoms[i % rl]) | 0;

    18. Влад
      11 августа 2011

      Сергей извините за возможный вопрос не в тему, но планируется ли сделать для Аймобилки RSS с новыми товарами?

    19. Сергей Чикуенок
      11 августа 2011

      Да, сделаем обязательно

    20. Влад
      11 августа 2011

      Благодарю за ответ! Сергей а не рассматривали ли вы возможность комбинированной подписки(допустим человек покупает подписку на месяц и получает возможность скачать 10 книг и 10 аудио книг за месяц)?

    21. Tim
      11 августа 2011

      Получилось красиво 🙂

      >Пробовали переместить шум в отдельную повторяющуюся картинку и наложить ее поверх картинки с >кулисами?
      >>Это не поможет: у картинки с кулисами разная освещённость в разных областях.

      Как я вижу, освещённость симметрична относительно вертикальной оси, проходящей по центру картинки.
      Возможно получится случайно генерить только левую половину шума, а правую — копировать, симметрично отражая. Может ускорить чуть ли на в два раза.

      Ещё я бы поигрался с разворачиванием цикла. Генерить не один пиксель в теле цикла, а сразу штук восемь. Практически, просто накопипастить.

    22. kvakazyambra
      12 августа 2011

      А чем и как меряли время исполнения скрипта? У меня в FF 4.0 и хроме шум появляется в последнюю очередь, после загрузки всех фоновых и контентных картинок. А файербаговский профайлер утверждает, что время исполнения noiseGenerator на разном железе (к слову, не самом медленном) от 1000 до 1500 мс. Пробовал на нет-буке — там вообще все печально: 3500 мс.

    23. Mila
      13 августа 2011

      Насчет фона — грузится прекрасно, а вот ссылочка «Вход и регистрация» у вас при ховере меняет цвет, а ее иконка — нет. Не очень-то красиво…

    24. Nail13
      13 августа 2011

      Сергей, ваша инженерная креативность не перестает радовать.
      Хотелось бы узнать, тем не менее, ваше отношение к полученному результату.
      С одной стороны, вы уменьшили объем подгружаемых данных аж на 200 кБ! Хотя в наши времена высокоскоростного интернета это достижение вряд ли кому-то удалось бы оценить.
      С другой стороны, вы обеспечили свое решение дополнительными костылями, в виде отдельно выполняемого кода, нагрузки процессора, выделяемой памяти и потенциальных проблем с кроссбраузерностью.

    25. Денис
      14 августа 2011

      Сергей, не понимаю почему у вас возникли сложности с шумом отдельной картинкой с повторением?

      По-моему задача решается небольшой подготовкой «шума» в фотошопе:
      полученный с помощью фильтра слой инвертируем и кладем маской для слоя залитого черным цветом, играем с прозрачностью, сохраняем, и вуаля — http://float-left.ru/im/

    26. Сергей Чикуенок
      16 августа 2011

      А чем и как меряли время исполнения скрипта? У меня в FF 4.0 и хроме шум появляется в последнюю очередь, после загрузки всех фоновых и контентных картинок.

      Просто замерял чистое время выполнения скрипта. Я посмотрю, почему могут быть такие проблемы со скоростью, спасибо за репорт.

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

      Если смотреть с этой стороны, то всякие новомодные border-radius и box-shadow грузят процессор не меньше. В целом — да, есть такая проблема и я о ней написал в конце. Она решаемая, но меня, в первую очередь, интересует это с точки зрения эксперимента. Потому что на подходе ещё несколько страниц с более тяжёлой графикой, но меньшими площадями «воздействия», там я тоже хочу попробовать применить такой же подход.

      По-моему задача решается небольшой подготовкой «шума» в фотошопе:
      полученный с помощью фильтра слой инвертируем и кладем маской для слоя залитого черным цветом, играем с прозрачностью, сохраняем, и вуаля — http://float-left.ru/im/

      У вас получились грязные брызги вместо лёгкого шума. Сравните:

    27. 16 августа 2011

      Я бы попробовал генерить шум на небольшом участке (скажем, 64×64), а потом циклически его повторять. Это если хочется именно генерить. Хотя задача, в самом деле, решается наложением png с чёрным полупрозрачным шумом.

    28. Сергей Чикуенок
      16 августа 2011

      Давид, попробуйте размножить кусок шума в обычном режиме наложения и посмотрите на результат. Вышу в комментариях уже есть пример.

    29. 16 августа 2011

      Ну уменьшите прозрачность немного. Разница между Normal и Multiply, конечно, есть, но она больше вкусовая в данном случае.

      Но даже если хочется именно Multiply, то всё равно можно здорово сэкономить, используя циклическую сгенерённую текстуру.

      P. S. У меня в хроме тоже шум накладывается в самую последнюю очередь, после загрузки обложек.

    30. Сергей Чикуенок
      16 августа 2011

      Я специально вынес добавление шума на onload картинки с кулисами, чтобы не блокировали отрисовку страницы. Всё-таки шум — это вспомогательный элемент, а не основной. В любом случае буду следить за тем, как развивается этот эксперимент.

    31. Денис
      16 августа 2011

      У вас получились грязные брызги вместо лёгкого шума. Сравните:

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

    32. Денис
      16 августа 2011

      Картинку не пропустило:
      http://float-left.ru/im/sample.png

    33. Сергей Чикуенок
      16 августа 2011

      Денис, а вы покажите пример целиком

    34. Денис
      16 августа 2011

      Лежит по тому же адресу — http://float-left.ru/im/

    35. Сергей Чикуенок
      16 августа 2011

      http://float-left.ru/im/ — в целом, готов признать, что разница не так сильно заметна по сравнению с оригиналом. Но лично у меня немного рябит в глазах не пересвеченых участках 🙂

    36. Сергей Чикуенок
      16 августа 2011

      Денис, я немного обновил статью, поставил ссылку на ваш пример.

    37. Денис
      16 августа 2011

      Ну так нижняя картинка ваша 🙂 Возможно при использовании на сайте, когда над фоном выводится содержимое, его «пересвеченные участки» не так оттягивают на себя внимание.

      А вообще это конечно же proof of concept, потратив еще немного времени можно избавиться от всех шероховатостей.
      Хотя экперимент с реализацией режимов смешивания на js очень интересен, исходная задача решается куда более тривиально.

    38. Сергей Чикуенок
      16 августа 2011

      Нет, я имел в виду пример в целом. Скорее всего, рябит из-за повторяющейся текстуры, хотя конкретно в моём случае этого не было бы заметно.

    39. Евгений Бойко
      24 августа 2011

      Вот ты задрачиваешься. Крутота нечеловеческая, молодец 🙂

    40. Антон
      30 августа 2011

      Хммм, странно видеть свой дизайн от чужих людей…
      Год назад рисовал для одного театра http://s46.radikal.ru/i113/1108/59/601f0cd2b958.jpg (вариант принят не был).

    41. kvakazyambra
      30 августа 2011

      Крепитесь! Здесь ваш дизайн уже воплощен в живой сайт: http://www.best-d.ru/ Можно подать в суд.

      А здесь http://rylik.ru/tags/%F2%E5%E0%F2%F0/ ваш дизайн можно скачать в формате psd. Они, сволочи, у вас исходники потырили. Готовьте иск — дело верное.

      Хм… а вот эти, похоже, сами вас могут засудить http://inetio.ru/entries/create/268?page=1 Их-то работа датирована 2008 годом. Старше вашей. Что скажете в свое оправдание?

      А вообще, сдается мне, что красные занавески — не ваше изобретение 🙂
      http://www.freelancerbay.com/files/users/Haizy/portfolio/8979_%D1%82%D0%B5%D0%B0%D1%82%D1%80%20860-874.jpg
      http://www.free-lancers.net/posted_files/N9C901070C607.jpg
      st.free-lance.ru/users/ivadesign/upload/f_496c92374ee43.png
      st.free-lance.ru/users/CheRya/upload/f_4bc58b6fe8d75.jpg
      http://www.site4u.kz/upload/screen/44143_410127_16.jpg
      http://www.d-p74.ru/media/primers/dizajn-sajta-teatra-opery-i-baleta-im-miglinki/chelnepr.jpg

    42. Антон
      30 августа 2011

      kvakazyambra, inetio.ru и правда первее.
      остальные — либо другие, либо позже. Про суд, конечно глупость, все равно мой вариант заказчиком не был принят.

    43. Сергей
      14 сентября 2011

      Офигенно сделано всё! Сейчас зашел на Аймобилко и охренел просто! таак быстро все загрузило..с моим то модемным интернетом. Круто в общем )

    44. daniyar
      18 сентября 2011

      Очередной раз убил подходом к делу. Респект, Сергей.

    45. Михаил
      9 октября 2011

      Сергей, расскажите, пожалуйста, каким образом вы проверяете скорость выполнения скрипта?

    46. Сергей Чикуенок
      10 октября 2011

      Михаил, это проверяется замером времени выполнения куска кода через (new Date).getTime(), либо через профайлер в Firebug и Web Inspector

    47. Михаил
      10 октября 2011

      Просто я пробовал получать время, используя getTime — уж слишком разброс в результатах большой получается. А профайлер из Firebug почему-то показывает только скорость работы участков jquery.

    48. romanov
      15 ноября 2011

      А как вы решили проблемы с IE, который вовсе не поддерживает canvas?

    49. 6 января 2012

      romanov, да плевать на этот недобраузер. На крайний случай можно написать процедуру определения юзер-агента и выдавать юзеру с IE толстую картинку. Автору хвала и слава, аж захотелось постичь канвас HTML5.

    50. Maxim
      16 января 2012

      Давненько ничего нового не появлялось! Сергей, Вы забросили сайт?

    51. romanov
      17 января 2012

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

    52. shr
      20 января 2012

      в демо когда листаешь список режимов клавиатурой картинки не пересчитываются. наверное, вместо onclick надо какой-нибудь там onchange

    53. 6 марта 2012

      В видео формате урок есть? Хотя и так написано не плохо. Просо хочу сахранить если забуду.

    54. Артем
      20 апреля 2012

      А почему не сделать PNG/GIF с шумом и прозрачностью но скажем 100х100 и не положить ее на картинку с занавесками? Сорри если уже было в комментах.

    55. 10 июля 2012

      проблем в PNG с альфаканалом не бывает ???

    56. Дмитрий
      8 августа 2012

      Не нашел вашего емаила. Есть вопрос-предложение.

    57. 9 сентября 2012

      суперский подход.вы молодец

    58. 1 ноября 2012

      Прекрасное изложение материала

    59. 6 ноября 2012

      Полностью соглашусь с Дамиром, материал суперский))