Постановка задачи: из исходного изображения
получить отраженное изображение:
Дополнительное условие: генерировать изображение на стороне клиента (то есть без использования 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
. Тем не менее, не все знают, что можно задавать градиентную прозрачность.
Перейдём к решению. Пусть у нас имеется такая разметка:
<img id="image" src="image.png" alt="Image"/>
</div>
Будем полагать, что ширина и высота контейнера заданы (если мы делаем отражение по вертикали, то ширина контейнера совпадает с шириной рисунка, а высота контейнера в два раза больше высоты рисунка; аналогично для горизонтального отражения).
Начнём с классики (MSIE).
* @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-технологию?)
* @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
В 90% на заданы. Я не тестил пример, но если критично указание явных размеров, но решение спросом пользоваться не будет
Всё равно всё просто Я сэкономил на нескольких строчках в JS, чтобы не вносить путаницу.
Для вертикального отражения: ширина контейнера — это ширина рисунка, а высота — две высоты рисунка.
Для горизонтального отражения: ширина контейнера — это две ширины рисунка, а высота — высота рисунка.
Написал на коленке:
* @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:
JavaScript:
В результате скрипт обернет image в
div
и задаст ему требуемую высоту и ширину.Да тоже думал когда попробывать реализовать… тока уровня знаний не хватило. А теперь нужда отпала, потому что готово. Спасибо.
Вопрос не втему, но я думаю по адресу, чем отличается clientHeight от offsetHeight
PS на маленьком разрешении (800х600) боковая колонка падает… нехватватет страничке min-width:
По-моему, так:
clientHeight=paddingTop+height+paddingBottom
Или так: http://www.quirksmode.org/viewport/elementdimensions.html
есть стати лекарство которое лечит IE от незнания canavas
http://sourceforge.net/project/showfiles.php?group_id=163391