Создание отражения рисунка с помощью JavaScript

HTML 5 приходит на помощь

Постановка задачи: из исходного изображения

Исходное изображение

получить отраженное изображение:

Исходное изображение с отражением

Дополнительное условие: генерировать изображение на стороне клиента (то есть без использования GD, ImageMagick и иже с ними).

В черновике стандарта  5 пристутствует такой замечательный элемент как <canvas>. Если вкратце, то данный элемент предназначен для создания изображений при помощи .

Впервые элемент <canvas> был представлен компанией Apple и использовался как компонент WebKit для Mac OS X в таких приложениях как Dashboard и Safari.

Поддержка <canvas> в Gecko появилась в версии 1.5, в Presto с версии 9.0 веб-браузера Opera. Текущие версии Internet Explorer (включая восьмую бету) не поддерживают <canvas>. Несмотря на это, для поставленной задачи существует кросс-браузерное решение.

В Microsoft Internet Explorer отражения с прозрачностью можно достичь путём использования комбинации фильтров. В частности, для вертикального отражения используется фильтр flipv, для горизонтального — fliph. Про реализацию прозрачности в Internet Explorer, наверное, знает каждый: progid:DXImageTransform.Microsoft.Alpha. Тем не менее, не все знают, что можно задавать градиентную прозрачность.

Перейдём к решению. Пусть у нас имеется такая разметка:

[-]
View Code HTML
<div id="container">
    <img id="image" src="image.png" alt="Image"/>
</div>

Будем полагать, что ширина и высота контейнера заданы (если мы делаем отражение по вертикали, то ширина контейнера совпадает с шириной рисунка, а высота контейнера в два раза больше высоты рисунка; аналогично для горизонтального отражения).

Начнём с классики (MSIE).

[-]
View Code Javascript
/**
 * @param container <div id="container">
 * @param image <img id="image">
 * @param ratio Коэффициент сжатия/растягивания отражения
 * @param opacity_start Начальная (ближняя к отражаемому изображению) непрозрачность
 * @param opacity_end Конечная непрозрачность
 * @param horizontal 0 = вертикальное отражение, 1 = горизонтальное отражение
 */

function reflect_image(container, image, ratio, opacity_start, opacity_end, horizontal)
{
    var reflection  = document.createElement('img');
    reflection.src = image.src;
    reflection.style.width = image.width + 'px';

    if (0 == horizontal) {
        reflection.style.filter = 'flipv progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity_start*100)+', style=1, finishOpacity='+(opacity_end*100)+', startx=0, starty=0, finishx=0, finishy='+(ratio*100)+')';
    }
    else {
        reflection.style.filter = 'fliph progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity_start*100)+', style=1, finishOpacity='+(opacity_end*100)+', startx=0, starty=0, finishx='+(ratio*100)+', finishy=0)';
    }

    container.appendChild(reflection);
}

В случае с IE всё просто и сводится к заданию соответствующих фильтров. С браузерами, поддерживающими HTML 5, всё гораздо сложнее (ad deliberandum: сколько строк занимает программа на C++, если она использует COM-технологию?)

[-]
View Code Javascript
/**
 * @param container <div id="container">
 * @param image <img id="image">
 * @param ratio Коэффициент сжатия/растягивания отражения
 * @param opacity_start Начальная (ближняя к отражаемому изображению) непрозрачность
 * @param opacity_end Конечная непрозрачность
 * @param horizontal 0 = вертикальное отражение, 1 = горизонтальное отражение
 */

function reflect_image(container, image, ratio, opacity_start, opacity_end, horizontal)
{
    var reflection = document.createElement('canvas');
    var context   = reflection.getContext('2d');
    reflection.style.height = image.height + 'px';
    reflection.style.width  = image.width + 'px';
    reflection.height       = image.height;
    reflection.width        = image.width;

    //Подводный камень: на canvas нельзя рисовать, если она не в DOM-дереве документа
    container.appendChild(reflection);

    context.save();
    var gradient;

    if (0 == horizontal) {
        // Задаём точку отсчёта, отражаем ось y и создаём вертикальный градиент
        context.translate(0, image.height);
        context.scale(1, -1);
        gradient = context.createLinearGradient(0, 0, 0, image.height);
    }
    else {
        // Задаём точку отсчёта, отражаем ось x и создаём горизонтальный градиент
        context.translate(image.width, 0);
        context.scale(-1, 1);
        gradient = context.createLinearGradient(0, 0, image.width, 0);
    }

    // Рисуем исходное изображение (оно будет отражено по одной из осей)
    context.drawImage(image, 0, 0, image.width, image.height);
    context.restore();

    // Задаём режим операции
    context.globalCompositeOperation = "destination-out";

    // Параметры градинтной заливки (в параметрах передаётся НЕпрозрачность, нам нужна прозрачность)
    gradient.addColorStop(1, "rgba(255, 255, 255, " + (1 - opacity_end) + ")");
    gradient.addColorStop(0, "rgba(255, 255, 255, " + (1 - opacity_start) + ")");
    context.fillStyle = gradient;

    // Заливаем
    if (-1 != navigator.appVersion.indexOf('WebKit')) {
        context.fill();
    }
    else {
        context.fillRect(0, 0, image.width, image.height);
    }
}

Тестовый пример.

Canvas Tutorial в Mozilla Developer Center

Вложения:

Автор: ; опубликовано в: JavaScript; метки: canvas, HTML, JavaScript, эффекты
17
Ноя
2008

RSS Комментарии к статье «Создание отражения рисунка с помощью JavaScript» (9)  »

  1. Будем полагать, что ширина и высота контейнера заданы

    В 90% на заданы. Я не тестил пример, но если критично указание явных размеров, но решение спросом пользоваться не будет :(

  2. Всё равно всё просто :-) Я сэкономил на нескольких строчках в JS, чтобы не вносить путаницу.

    Для вертикального отражения: ширина контейнера — это ширина рисунка, а высота — две высоты рисунка.
    Для горизонтального отражения: ширина контейнера — это две ширины рисунка, а высота — высота рисунка.

    Написал на коленке:

    [-]
    View Code Javascript
        /**
         * @param image document.getElementById('image')
         * @param h_coef Коэффициент растягивания отражения
         * @param opacity_start Начальное значение непрозрачности
         * @param opacity_end Конечное значение непрозрачности
         * @param mode 0 = вертикальное отражение, 1 = горизонтальное
         */

        function add_reflection(image, h_coef, opacity_start, opacity_end, mode)
        {
            var container  = document.createElement('div');
            var ref_height, ref_width, div_height, div_width;
           
            if (0 == mode) {
                ref_height = Math.floor(image.height * h_coef);
                ref_width  = image.width;
                div_height = Math.floor(image.height * (1 + h_coef));
                div_width  = ref_width;
            }
            else {
                ref_width  = Math.floor(image.width * h_coef);
                ref_height = image.height;
                div_height = ref_height
                div_width  = Math.floor(image.width * (1 + h_coef));
            }
           
            var reflection;
           
            if (!window.opera && document.all) {
                reflection = document.createElement('img');
                reflection.src = image.src;
                reflection.style.width = image.width + 'px';
               
                if (0 == mode) {
                    reflection.style.filter = 'flipv progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity_start*100)+', style=1, finishOpacity='+(opacity_end*100)+', startx=0, starty=0, finishx=0, finishy='+(h_coef*100)+')';
                }
                else {
                    reflection.style.filter = 'fliph progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity_start*100)+', style=1, finishOpacity='+(opacity_end*100)+', startx=0, starty=0, finishx='+(h_coef*100)+', finishy=0)';
                }

                container.style.width  = div_width + 'px';
                container.style.height = div_height + 'px';
                image.parentNode.replaceChild(container, image);
                container.appendChild(image);
                container.appendChild(reflection);
            }
            else {
                reflection  = document.createElement('canvas');
                var context = reflection.getContext('2d');
                reflection.style.height = ref_height + 'px';
                reflection.style.width  = ref_width + 'px';
                reflection.height       = ref_height;
                reflection.width        = ref_width;
               
                container.style.width  = div_width + 'px';
                container.style.height = div_height + 'px';
                image.parentNode.replaceChild(container, image);
                container.appendChild(image);
                container.appendChild(reflection);
               
                context.save();
                var gradient;
               
                if (0 == mode) {
                    context.translate(0, image.height);
                    context.scale(1, -1);
                    gradient = context.createLinearGradient(0, 0, 0, ref_height);
                }
                else {
                    context.translate(image.width, 0);
                    context.scale(-1, 1);
                    gradient = context.createLinearGradient(0, 0, ref_width, 0);
                }
               
                context.drawImage(image, 0, 0, image.width, image.height);
                context.restore();
               
                context.globalCompositeOperation = "destination-out";
                gradient.addColorStop(1, "rgba(255, 255, 255, " + (1 - opacity_end) + ")");
                gradient.addColorStop(0, "rgba(255, 255, 255, " + (1 - opacity_start) + ")");
                context.fillStyle = gradient;
                if (-1 != navigator.appVersion.indexOf('WebKit')) {
                    context.fill();
                }
                else {
                    context.fillRect(0, 0, image.width, 2*ref_height);
                }
            }
        }

    Как пользоваться: HTML:

    [-]
    View Code HTML
    <img id="image" src="image.png" alt=""/>

    JavaScript:

    [-]
    View Code Javascript
    add_reflection(document.getElementById('image'), 1, 1, 0.1, 0);

    В результате скрипт обернет image в div и задаст ему требуемую высоту и ширину.

  3. Да тоже думал когда попробывать реализовать… тока уровня знаний не хватило. А теперь нужда отпала, потому что готово. Спасибо.

    Вопрос не втему, но я думаю по адресу, чем отличается clientHeight от offsetHeight

    PS на маленьком разрешении (800х600) боковая колонка падает… нехватватет страничке min-width:

  4. есть стати лекарство которое лечит IE от незнания canavas
    http://sourceforge.net/project/showfiles.php?group_id=163391

  5. Бодя

    Привет. Я поработал с твоим кодом, думаю у тебя мелкая ошибка, вместо 2*ref_height должно быть image.height.

  6. Бодя

    не атачится

    [-]
    View Code Javascript
        document.myGetElementsByClassName = function(className) {
            var children = document.getElementsByTagName('img');
            var elements = [];
         
            for (var i = 0; i < children.length; i++) {
                var child = children[i];
                if(child.getAttribute('alt')=='reflect')
                        elements.push(child);
            }
            return elements;
        }

    function addReflections(){
        var rimages = document.myGetElementsByClassName('reflect');
        for (i=0;i<rimages.length;i++) {
            reflect_image(rimages[i])
        }
    }


    function reflect_image(image)
    {
            if(!image.complete)
                return;

        var k       = 3;  // aka ratio
            var opacity_start = 0.31;
            var opacity_end   = 0.001;
            var mode          = 1;  //0 = vertical, 1 = horizontal  

            var container  = image.parentNode, replace;
            if(replace = container.childNodes.length!=1){
                container  = document.createElement('div');
            }
            container.style.display='inline-block';
             
            var
                ref_height = image.offsetHeight,
                ref_width  = image.offsetWidth;

           
            var reflection;
           
             if (!window.opera && document.all){
                reflection = document.createElement('img');
            }else{
                reflection  = document.createElement('canvas');
                var context = reflection.getContext('2d');
            }
           
           /* reflection.height       = ref_height;
            reflection.width        = ref_width;*/


            if(0 == mode){
                reflection.style.display = 'block';
                reflection.style.marginTop  = '1px';
                reflection.style.height = ref_height/k + 'px';
                reflection.style.width  = ref_width + 'px';
            }else{
                reflection.style.cssFloat  = 'left';
                reflection.style.marginRight  = '1px';
                reflection.style.height = ref_height + 'px';
                reflection.style.width  = ref_width/k + 'px';
            }

            /*container.style.width  = div_width + 'px';
            container.style.height = div_height + 'px';*/

               
            if (!window.opera && document.all) {
                reflection.src = image.src;
               
                if (0 == mode) {
                    reflection.style.filter = 'flipv progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity_start*100)+', style=1, finishOpacity='+(opacity_end*100)+', startx=0, starty=0, finishx=0, finishy='+(100)+')';
                }
                else {
                    reflection.style.filter = 'fliph progid:DXImageTransform.Microsoft.Alpha(opacity='+(opacity_end*100)+', style=1, finishOpacity='+(opacity_start*100)+', startx=0, starty=0, finishx='+(100)+', finishy=0)';
                }
            }
            else
            {
                context.save();
                var gradient;
                reflection.src = image.src;//только для того чтоб знать размеры…
                var z1=reflection.width, z2=reflection.height;
               
                if (0 == mode) {
                    context.translate(0, z2);
                    context.scale(1, -1);
                    gradient = context.createLinearGradient(0, 0, 0, ref_width);
                }
                else {
                    context.translate(z1, 0);
                    context.scale(-1, 1);
                    gradient = context.createLinearGradient(0, 0, ref_height, 0);
                }
               
                context.drawImage(image, 0, 0, z1, z2);
                context.restore();
               
                context.globalCompositeOperation = "destination-out";
                gradient.addColorStop(1, "rgba(255, 255, 255, " + (1 - opacity_start) + ")");
                gradient.addColorStop(0, "rgba(255, 255, 255, " + (1 - opacity_end) + ")");
                context.fillStyle = gradient;
                if (-1 != navigator.appVersion.indexOf('WebKit')) {
                    context.fill();
                }
                else {
                    context.fillRect(0, 0, z1, z2);
                }
            }

           
            if(replace){
                image.parentNode.replaceChild(container, image);
                container.appendChild(image);
            }
            container.appendChild(reflection);
    }
  7. ОХ ТЫ.. СПАСИБО ИСКАЛ НЕЧТО ПОХОЖИЕ…
    Хорошая статья.

Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.

Оставить комментарий к записи «Создание отражения рисунка с помощью JavaScript»

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Оставляя комментарий, вы выражаете своё согласие с Правилами комментирования.

Подписаться, не комментируя

गते गते पारगते पारसंगते बोधि स्वाहा