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

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

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

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

Впервые элемент <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

Добавить в закладки
  • del.ici.ous
  • Digg
  • Furl
  • Google
  • Simpy
  • Spurl
  • Y! MyWeb
  • БобрДобр
  • Мистер Вонг
  • Yandex.Закладки
  • Текст 2.0
  • News2
  • AddScoop
  • RuSpace
  • RUmarkz
  • Memori
  • Google Bookmarks
  • Писали
  • СМИ 2
  • Моё Место
  • 100 Закладок
  • Ваау!
  • Technorati
  • RuCity
  • LinkStore
  • NewsLand
  • Lopas
  • Закладки - IN.UA
  • Connotea
  • Bibsonomy
  • Trucking Bookmarks
  • Communizm
  • UCA
  • Slashdot
  • Magnolia
  • Blogmarks
  • Current
  • Meneame
  • Oknotizie
  • Diigo
  • Funp
  • Hugg
  • Dealspl.us
  • N4G
  • Mister Wong
  • Faves
  • Yigg
  • Fresqui
  • Care2
  • Kirtsy
  • Sphinn

Связанные записи

17
Ноя
2008

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

  1. Макисим Покровский says:

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

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

  2. Vladimir says:

    Всё равно всё просто :-) Я сэкономил на нескольких строчках в 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. Jman says:

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

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

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

  4. Jman says:

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

Подписаться на RSS-ленту комментариев к статье «Создание отражения рисунка с помощью JavaScript» Trackback URL: http://blog.sjinks.org.ua/javascript/356-image-reflection-with-javascript/trackback/

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

Вы можете использовать данные тэги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

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

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