Pull to refresh

Размытие изображений по Гауссу с помощью SVG

Website development *
Sandbox


В этом топике я хотел бы рассказать, как добавить изображениям на веб странице эффект размытия по Гауссу без использования флеша. Статью я планировал написать еще год назад, и, к моему большому удивлению, меня никто не опередил, хотя тема достаточно проста и интересна как с точки зрения веб дизайна, так и с точки зрения клиентской разработки. Для создания эффекта будем использовать уже ставшую популярной технологию SVG, которая работает во всех последних версиях браузеров. Для IE8- воспользуемся CSS фильтрами (Blur в частности), которые работают только в продуктах Microsoft. Для начала, предлагаю ознакомиться с итоговым вариантом здесь (наведите на любое изображение). Скрипт представлен в виде jQuery плагина, но выдернуть его для использования вне jQuery не составит труда.

Очевидно, целью работы являетя не только размытие картинки, а и анимация этого размытия.

Из простейшего примерамы видим, что интенсивность размытия зависит от атрибута stdDeviation в теге feGaussianBlur.

<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">

<defs>
<filter id="Gaussian_Blur">
<feGaussianBlur in="SourceGraphic" stdDeviation="3" />
</filter>
</defs>

<ellipse cx="200" cy="150" rx="70" ry="40"
style="fill:#ff0000;stroke:#000000;
stroke-width:2;filter:url(#Gaussian_Blur)"
/>

</svg>


* This source code was highlighted with Source Code Highlighter.

(код взят отсюда).
Первым делом я попробовал анимировать этот атрибут, но, как оказалось, значения атрибута могут быть только целыми числами, то есть анимация получалась “дерганная”. Для создания иллюзии плавной анимации, будем следовать очень простой логике.



Делаем два слоя: первый — размытое изображение (SVG), второй — исходное изображение (обычный html img), и меняем ему прозрачность, которая прекрасно анимируется. Для того, чтоб избежать проблем с позиционированием элементов относительно друг друга, было поставлено требование: исходное изображение должно содержаться в контейнере, например, в теге span, в который далее будет добавлена и размытая картинка.

<span class="blurImageContainer">
  <img class="blurImage" src="a.jpg">
</span>


* This source code was highlighted with Source Code Highlighter.

Первым делом был создан объект для удобной работы с SVG:
var SVG = {
  
  //пространства имен
  svgns: 'http://www.w3.org/2000/svg',
  xlink: 'http://www.w3.org/1999/xlink',
  
  //создание svg элемента (name - имя тега, attrs - атрибуты)
  createElement: function(name, attrs){
    var element = document.createElementNS(SVG.svgns, name);
    
    if(attrs) {
      SVG.setAttr(element, attrs);
    }
    return element;
  },
  
  //установка атрибутов (element - SVG-узел)
  setAttr: function(element, attrs) {
    for(var i in attrs) {
      if(i === 'href') { //атрибут href принадлежит пространству имен xlink
        element.setAttributeNS(SVG.xlink, i, attrs[i]);
      } else { //обычный атрибут
        element.setAttribute(i, attrs[i]);
      }
    }
  }
}


* This source code was highlighted with Source Code Highlighter.
Это всё, что нам понадобится для корректного создания нескольких svg элементов.

Для того чтоб не допустить ошибки многоразового применения плагина к одному и тому же элементу, каждый раз будем удалять элемент с размытием, а для этого нам нужно его как-то идентифицировать. К сожалению, добавить класс к самому svg элементу не удалось, зато получилось присвоить ему айдишник:
var blurredId = Math.random(); //уникальный айдишник для размытого изображения
...
$this.children('[id^="blurred"]').remove(); //удаление размытого элемента при повторном использовании плагина
...
        
svg = SVG.createElement('svg', {
  ...
  id: 'blurred'+blurredId
});


* This source code was highlighted with Source Code Highlighter.
Это позволит нам так же избежать конфликта айдишников фильтра размытия, на который ссылается изображение.

В итоге мы получаем следующий код:
var svg, filterId, filter, gaussianBlur, image;      
svg = SVG.createElement('svg', { //сам SVG элемент
  xmlns: SVG.svgns,
  version: '1.1',
  
  //ширина и высота искомого изображения
  width: imgWidth,
  height: imgHeight,
  
  id: 'blurred'+blurredId
});

filterId = 'blur'+blurredId; //айдишник фильтра; к нему обращается элемент image
filter = SVG.createElement('filter', { //фильтр
  id:filterId
});

gaussianBlur = SVG.createElement('feGaussianBlur', { //элемент размытия по Гауссу
  'in':'SourceGraphic', //in — ключевое слово; в опере будет ошибка, если кавычек не поставить
  stdDeviation: args.deviation //интенсивность размытия (int)
});
      
image = SVG.createElement('image', { //изображение, к которому применяется размытие
  x: 0,
  y: 0,
  
  //ширина, высота и адрес искомого изображения
  width: imgWidth,
  height: imgHeight,
  href: imgSrc,
  
  style: 'filter:url(#'+filterId+')' //ссылка на фильтр
});

filter.appendChild(gaussianBlur); //добавляем тег размытия в тег фильтра
svg.appendChild(filter); //добавляем фильтр в SVG
svg.appendChild(image); //добавляем размытое изображение в SVG
this.appendChild(svg); //добавляем SVG в span (this) с искомым изображением


* This source code was highlighted with Source Code Highlighter.

который генерирует такой SVG:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="144" height="144" id="blurred0.9918661566916853">
 <filter id="blur0.9918661566916853">
  <feGaussianBlur in="SourceGraphic" stdDeviation="2"></feGaussianBlur>
 </filter>
 <image x="0" y="0" width="144" height="144" href="a.jpg" style="filter:url(#blur0.9918661566916853)"></image>
</svg>


* This source code was highlighted with Source Code Highlighter.


Теперь приступим к размытию в IE ранних версий. Как говорилось выше, будет использоваться фильтр Blur. Получаемое размытие не совсем по Гауссу, да и выглядит не очень, но это не столь важно. Придерживаемся аналогичного принципа: создаем второе изображение, кладем его под исходное, размываем.
$img.clone() //клонируем искомое изображение
  .css({ //добавляем стили

    //сам фильтр; здесь интенсивность размытия помноженная на два, примерно равна интенсивности в нормальных браузерах
    filter: 'progid:DXImageTransform.Microsoft.Blur(pixelradius=' + args.deviation*2 + ')',
    
    //выравниваем размытое изображение по вертикали и горизонтали, относительно искомого
    top: -args.deviation*2,
    left: -args.deviation*2,
    
    //почему-то высоты и ширины у изображений разные; фиксим
    width: imgWidth,
    height: imgHeight,
  })
  .attr('id', 'blurred'+blurredId)
  .appendTo(this);


* This source code was highlighted with Source Code Highlighter.

Оформляем скрипт в виде jQuery плагина и используем следующим образом:
jQuery(window).load(function($){ //нам нужно иметь доступ к размерам картинки, поэтому испольуем load вместо ready
  $('.blurImageContainer .blurImage').css({opacity: 0}); //делаем исходную картинку полностью прозрачной
  
  $('.blurImageContainer').gaussianBlur({
    deviation: 3, //уровень размытия
    imageClass: 'blurImage'  //класс искомого изображения  
  });
  
  $('.blurImageContainer').hover(function(){
    $('.blurImage', this).animate({opacity: 1}, 500);
  },function(){
    $('.blurImage', this).animate({opacity: 0}, 500);
  });
});


* This source code was highlighted with Source Code Highlighter.


Спасибо за внимание.
Прошу сообщать о любых неточностях и недочетах.

Ссылки:
finom.ho.ua/blur — пример
finom.ho.ua/blur/jquery.gaussian-blur.js — код плагина
www.w3.org/TR/SVG/filters.html — спецификация фильтров в SVG

P. S. В процессе работы, была предпринята попытка научиться размывать не только картинки, но и целые куски html. Не смотря на то, что встроить html в svg (именно так) не составляет труда, применить графические фильтры не удалось. Буду рад, если уважаемые хабралюди помогут с идеей, как это сделать.
Tags:
Hubs:
Total votes 115: ↑112 and ↓3 +109
Views 15K
Comments Comments 46