English
🏷️

Кросс-браузерное определение реальных размеров изображения с помощью jQuery  

Это статья из старого первого блога, который сохранился только на archive.org. Все старые статьи помечены тегом 🚨 старое

Кросс-браузерное определение реальных размеров изображения с помощью jQuery и вписывание его в область с заранее заданными размерами. Должно получиться следующе

Итак, дано: на странице имеются уже загруженные изображения немаленького размера.

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

Знаю, что грузить сразу и много изображений, потом незаметно их уменьшать на клиенте — полный маразм, но так получилось, но сейчас не об этом :)

HTML прост:

HTML

    <a class="link link--info" href="images/img-1.jpg" class="img-link">
      <img src="images/img-1.jpg" alt="" class="img" width="100" height="100" />
    </a>
    <a class="link link--info" href="images/img-2.jpg" class="img-link">
      <img src="images/img-2.jpg" alt="" class="img" width="100" height="100" />
    </a>
    <a class="link link--info" href="images/img-N.jpg" class="img-link">
      <img src="images/img-N.jpg" alt="" class="img" width="100" height="100" />
    </a>
    ...

    <a class="link link--info" href="images/img-20.jpg" class="img-link zoom-img">
      <img src="images/img-20.jpg" alt="" class="img" width="100" height="100" />
    </a>
    

Так как информация о размере изображения нам будет доступна, только когда это изображение браузер загрузит — будем использовать событие .load(). По этому событию с изображением будут выполняться все необходимые процедуры:

JavaScript

    // Берем все необходимые нам изображения.
    var $img = $('.img');

    // Дожидаемся загрузки изображения браузером.
    $img.load(function(){

        // Здесь будет много разных букв.
    });
    

Если у изображения будут заданы ширина или высота атрибутами, либо в CSS — «реальными» размерами будут именно эти размеры, поэтому обезопасим себя, в самом начале удалив все эти заранее заданные размеры, если конечно они имеются:

JavaScript

    // Кладем указатель на изображение в переменную, 
    // дабы каждый раз не вызывать функцию и не нагружать дополнительно браузер.
    var CurImg = $(this);

    // Если заданы атрибуты width и height - удаляем их.
    CurImg.removeAttr("width")
      .removeAttr("height")

      // А также перестраховываемся насчет CSS-свойств.
      .css({ width: "", height: "" });
    

С помощью методов .width() и .height(), выясняем размеры изображения:

JavaScript

    // Получаем реальные ширину и высоту.
    var width  = CurImg.width();
    var height = CurImg.height();
    

По условию наше изображение должно вписываться в область 100×100 пикселей, поэтому напишем соответствующие условия задания атрибутов для пропорционального масштабирования:

JavaScript

    // Если изображение больше допустимой ширины 
    // или высоты (maxSize), задаем ее соответствующими атрибутами.
    var maxSize = 100;
    if (width > height) CurImg.attr('width', maxSize);
    else if (height > width) CurImg.attr('height', maxSize);
    else if (height == width) CurImg.attr('height', maxSize);
    

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

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

Насчет горизонтального выравнивания все просто, изображению мы зададим margin:auto; А вот с вертикальным придется помудрить.

Для нормальных браузеров можно обойтись двумя правилами для контейнера: display: table-cell; и vertical-align: middle;. То есть заставить ссылку вести себя как ячейка таблицы.

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

Но мы пойдем простым путем и сразу для всех запишем там же, в нашей функции, следующий кодик:

JavaScript

    // Вычисляем и задаем необходимый отступ сверху 
    // у изображения для того чтобы отцентрировать его по вертикали.
    var imgHeight = CurImg.height();
    var marginTop = (maxSize - imgHeight)/2;
    CurImg.css('margin-top', marginTop)
    

Пока мы манипулируем с изображением, которое уже загружено в браузер, изображения на долю секунды будут размером 100×100 пикселей, потом когда удалятся атрибуты — реального размера, что будет «разрывать» нашу страницу, затем только отмасштабируются. Думаю пользователя пугать не стоит и логичнее было бы сразу задать изображениям: display:none;

А затем в функции, после всех манипуляций выставить display:block;.

Сразу после выполнения всех необходимых действий над изображением, добавим разметку для отображения инфо-значка увеличения изображения <span class=“zoom-icon”></span>, который будет показываться по ховеру на ссылке. В итоге вся конструкция будет выглядеть следующим образом:

JavaScript

    // Берем все необходимые нам изображения.
    var $img = $('.img');

    // Дожидаемся загрузки изображения браузером.
    $img.load(function(){

    // Кладем указатель на изображение в переменную, дабы каждый раз 
    // не вызывать функцию и не нагружать дополнительно браузер.
    var CurImg = $(this);

    // Если заданы атрибуты width и height - удаляем их.
    CurImg.removeAttr("width")
      .removeAttr("height")
      // А также перестраховываемся насчет CSS-свойств.
      .css({ width: "", height: "" });

    // Получаем реальные ширину и высоту.
    var width  = CurImg.width();
    var height = CurImg.height();

    // Если изображение больше допустимой ширины или высоты (maxSize), 
    // задаем ее соответствующими атрибутами.
    var maxSize = 100;
    if (width > height) CurImg.attr('width', maxSize);
    else if (height > width) CurImg.attr('height', maxSize);
    else if (height == width) CurImg.attr('height', maxSize);

    // Вычисляем и задаем необходимый отступ сверху у изображения 
    // для того чтобы отцентрировать его по вертикали.
    var imgHeight = CurImg.height();
    var marginTop = (maxSize - imgHeight)/2;
    CurImg.css('margin-top', marginTop)

    // Показываем отмасштабированное изображение.
    .css('display', 'block');

    // Добавляем разметку для отображения значка по ховеру.
    CurImg.parent().append('<span class="zoom-icon"></span>');
});
    

Все это будет расположено после HTML с изображениями.

В области head создадим объект изображение, тем самым подгрузив предварительно анимированный значок-предзагрузчик, который будет задан ссылке бэкграундом, чтобы пользователь не пугался пустого места и ему было не скучно :) на время манипуляций с изображениями:

JavaScript

    // Создаем объект Image, тем самым заставляя браузер 
    // предварительно загрузить анимированное изображение прелоадера.
    preloaderImg = new Image();
    preloaderImg.src="images/preloader.gif";
    

Немного ниже расположим код вызова плагина fancybox, для красивого всплывания полноразмерного изображения, по клику. Конструкция в области head будет выглядеть так:

JavaScript

    // Создаем объект Image, тем самым заставляя браузер 
    // предварительно загрузить анимированное изображение прелоадера.
    preloaderImg = new Image();
    preloaderImg.src="images/preloader.gif";
    
    $(document).ready(function() {
      $(".img-link").fancybox({
        'titlePosition' : 'inside',
        'transitionIn' : 'none',
        'transitionOut' : 'none',
        'centerOnScroll':true,
        'overlayOpacity':0.2,
        'titleShow': false,
        'transitionIn': 'elastic',
        'transitionOut': 'elastic',
        'overlayColor':'#000',
        'hideOnContentClick':true
      });
    });
    

СSS оформление:

CSS

    .img-link {
      width:100px;
      height:100px;
      border:1px solid #ccc;
      margin:10px;
      display:block;
      float:left;
      position:relative;
      background:#fff url("images/preloader.gif") center center no-repeat;
      overflow:hidden;
      }
      .zoom-icon {
          background:url("images/zoom.png") left top no-repeat;
          cursor:pointer;
          height:101px;
          left:-999px;
          position:absolute;
          top:17px;
          width:104px;
          z-index:100;
          }
    a.img-link:hover {background:#fff;}
        a.img-link:hover .zoom-icon {left:17px;}
        .img {display:none; margin:auto;}
    

И вуаля.

Но если эту страницу открыть в браузере Opera, все будет не совсем замечательно. Первый раз, когда изображения загрузятся с сервера, наша функция сработает. Но вот если затем нажать энтэр в строке браузера, либо опять перейти на эту же страницу по ссылке&nbps;— функция уже не сработает, и на странице так и останутся уныло вертеться двадцать прелоадеров :)

Связано это с тем, что наша любимая опера по-своему относится к изображениям загружающимся из кэша, и jQuery событие .load() не срабатывает для закешированных изображений. Но стоит очистить кэш и функция срабатывает снова. Хотя она срабатывает даже если просто нажать кавишу F5. Странно, но решение все же нашлось, в комментариях к методу .load() добрый человек предложил следующее:

JavaScript

    $('.img').one('load',function(){

    // Необходимые манипуляции с изображением.

    })
    .each(function(){
        if(this.complete) $(this).trigger('load');
    });
    

То есть сначала проверить с помощью «чистого» яваскрипт (this.complete) доступность изображения, а потом по триггеру — загрузку.

В опере прекрасно работает, но только с версии 11, возможно и 10 (проверял в 11.11). Opera 9 (проверял в 9.63) напрочь воспротивилась выполнять функцию даже с пустым кэшем. В остальных браузерах, а также в IE6 работает прекрасно.

Конструкция целиком:

JavaScript

    // Берем все необходимые нам изображения.
    var $img = $('.img');

    // В случае загрузки изображения единожды по триггеру 
    // (условие в самом низу) выполняем необходимые действия.
    $('.img').one('load',function(){

    // Кладем указатель на изображение в переменную, дабы каждый раз 
    // не вызывать функцию и не нагружать дополнительно браузер.
    var CurImg = $(this);

    // Если заданы атрибуты width и height - удаляем их.
    CurImg.removeAttr("width")
      .removeAttr("height")
      // А также перестраховываемся насчет CSS-свойств.
      .css({ width: "", height: "" });

    // Получаем реальные ширину и высоту.
    var width  = CurImg.width();
    var height = CurImg.height();

    // Если изображение больше допустимой ширины или высоты (maxSize), 
    // задаем ее соответствующими атрибутами.
    var maxSize = 100;
    if (width > height) CurImg.attr('width', maxSize);
    else if (height > width) CurImg.attr('height', maxSize);
    else if (height == width) CurImg.attr('height', maxSize);

    // Вычисляем и задаем необходимый отступ сверху у изображения 
    // для того чтобы отцентрировать его по вертикали.
    var imgHeight = CurImg.height();
    var marginTop = (maxSize - imgHeight)/2;
    CurImg.css('margin-top', marginTop)

    // Показываем отмасштабированное изображение.
    .css('display', 'block');

    // Добавляем разметку для отображения значка по ховеру.
    CurImg.parent().append('<span class="zoom-icon"></span>');
    })
    // Собственно сам триггер, проверяем каждое изображение 
    // на "готовность" и ставим триггер на загрузку.
    .each(function(){
        if(this.complete) $(this).trigger('load');
    });
    

Рабочий пример можно посмотреть здесь.

Скачать архив (архив пухлый из за специально не оптимизированных тяжелых изображений).