Category Archives: програмиране

Невидимо вграждане на външно съдържание с iframe

от Гонзо
лиценз CC BY-NC-SA

Поради различни съображения понякога се налага да използваме външни услуги за част от съдържанието в сайта. В един точно такъв случай ми се наложи да вградя външното съдържание в сайта с iframe и решението трябваше да отговаря на следните изисквания:

  • Височината на iframe-а винаги да отговаря на височината на вградената страница, за да няма втори скролбар.
  • При навигация във вградената страница да се променя и адреса в основната, като промененият адрес да зарежда съответната страница от външната услуга.
  • Заглавието на документа да се променя заедно със навигацията във вграденото съдържание.

Особеното е, че при зареждане на съдържание от друг домейн в iframe JavaScript от едната страница няма достъп до другата. Поради тази причина оразмеряването на iframe според съдържанието изглежда на пръв поглед проблемно. След малко търсене на най-доброто решение се оказа, че то лесно ще ми помогне за изпълнението и на другите две изисквания. Добре де, за втората точка пипнах малко и бекенда на основния сайт, но няма да навлизам в подробности, защото там не съм в свои води.

И така, най-разумното решение за изравняване на височината на iframe с неговото съдържание, когато то се зарежда от друг домейн, е чрез postMessage. Всеки път, когато се зареди страница в рамката, тя праща съобщение към родителската страница с височината на съдържанието. Родителската страница съответно като получи такова съобщение оразмерява рамката.

Вътре в рамката имаме това:

window.addEventListener('load', function(e){
  if(window.parent && window.parent.postMessage){
    var height = document.body.scrollHeight;
    window.parent.postMessage(height, '*');
  }
}, false);

А в основния сайт имаме това:

window.addEventListener('message', function(e) {
  var data = e.originalEvent.data;
  if (!data) return;
  document.getElementById('theframe').style.height = data + 'px';
}, false);

И след като можем да си изпратим някакви данни от единия прозорец към другия, тогава защо не си изпратим всички данни, от които имаме нужда? Какво ни трябва още? Адреса на страницата в рамката и заглавието на страницата. Всъщност няма да ни трябва целия адрес на страницата, а само пътя. Решението ми за синхронизиране на адресите на двете страници беше да добавя пътя от рамката като параметър към адреса на основната страница, и да го използвам за формиране на атрибута src на рамката при зареждане на страницата. Естествено, включих параметър като част от пътя в адреса на основната страница чрез URL Rewrite.

Това, което пропуснах е как точно ще променим адреса на основната страница като получим съобщението от рамката? В първия момент опитах да използвам history.pushState() – имаме навигация и искаме да дадем възможност потребителя да се върне назад и после напред и… имаше обслужване на събитието popstate и се увъртех като пиле в кълчища. Стана така, защото не взех предвид факта, че навигацията вътре в рамката прави записи в историята на браузъра и чрез pushState аз ги дублирам. Много по-лесно се оказа да използвам history.replaceState() – бутоните на браузъра за навигиране в историята управляват рамката, а чрез събитието popstate и postMessage данните за това се пращат към основната страница. Крайният код придоби следния вид:

iframe.js

function sendParent(e){
  if(window.parent && window.parent.postMessage){
    var height = document.body.scrollHeight,
        path = document.location.pathname,
        title = document.title;
    window.parent.postMessage({height: height, path: path, title: title}, '*');
  }
}
window.addEventListener('load', function(e){
  sendParent();
}, false);
window.addEventListener('message', function(e){
  var data = e.data || e.originalEvent.data;
  if(data == 'get') {
    sendParent();
  }
}, false);
window.addEventListener('popstate', function(e){
  setTimeout(sendParent, 0);
}, false);
document.addEventListener("page:load", function() {
  sendParent();
}, false);

parent.js

var theframe = document.getElementById('theframe');
window.addEventListener('message', function(e) {
  var data = e.originalEvent.data;
  if (!data) return;
  theframe.style.height = data.height + 'px';
  window.history.replaceState(null, '', '/help' + data.path);
  window.scroll(0, 0);
  document.title = data.title;
}, false);
window.addEventListener('resize', function(e) {
  theframe.contentWindow.postMessage('get', '*');
}, false);

На края на кода на рамката сигурно сте забелязали събитието page:load – оказа се, че услугата, която вграждаме използва turbolinks и това е събитието, което се случва когато turbolinks зареди съдържанието на нова страница. А на края на кода на основната старница трябва да ви е направило впечатление обработката на resize на прозореца – ако се промени размерът му е много вероятно да се промени и височината на съдържанието в рамката и тогава питаме рамката за размера на съдържанието. Сигурно първото нещо, което си мислите е „А що не го закачим направо на resize на рамката?“ – ами щото resize на рамката се случва и когато ѝ зададем нов размер, което ще изпрати още веднъж съобщение към прозореца, той пак ще сложи нов размер на рамката… Е, на втория – третия път спира тоя пинг-понг, ама кому е нужно…

Та те така, получи се съвсем невидимо за потребителя – зарежда се съдържание, линковете в рамката сменят като че ли цялата страница, бутоните „напред“ и „назад“ на браузъра работят както се очаква… Идилия! Бих ви го показал, но още не сме готови с някои други неща, ще сложа линка когато му дойде времето. Вервайте ми!

SVG filters and CSS animations don’t play together well in WebKit

от Гонзо
лиценз CC BY-NC-SA

There is one particular day in my life that went into reading everything I could find about applying SVG filters on HTML content in Webkit/Blink and pulling my hair why didn’t my code work. See, I had an element with a SVG filter applied as a URI reference and the filter didn’t appear in Chrome and Opera. I tryed embedding the filter definition into the HTML – didn’t work, I tryed recreating the filter with the filter functions available in CSS – could’t create the same effect, but filters did work. Thet I noticed that the filters shows up while the JavaScript is loading, and after disabling JS throu DevTools the filter was there. And after toggling every single piece of JavaScript on the website on and off I managet to pinpoint the cause of my trobbles – a function that applied a class to the body that triggered an animation on a element that is siblink to the one with the SVG filter applied. Then I remembered reading about Chrome not using the GPU for SVG filters applied with URI, but using it for the shortcut functions in CSS. And then I knew – when applying the animation, Chrome rendered the whole container with the GPU and the SVG filter disappeard. And I did try to use 2d functions for the animation, but Chrome still used the GPU and broke the filter. So, if you ever try to use complex SVG filters together with CSS animation, be prepared for trouble!

I know what you did onbeforeunload

от Гонзо
лиценз CC BY-NC-SA

There are a couple of questions on StackOverflow about distinguishing download links in onbeforeunload event handler, the usual use case being skipping loading animation. The simple solution is to use the download attribute on the link itself, but this can’t be applied for forms. I had the same problem and fortunately I found a solution.

What I did was the obvious thing of checking the properties of the event object, passed to the handler. In Firefox there is explicitOriginalTarget property and when the event is triggered by a form submission, the property is a reference to the submit button (if the form is submitted by pressing the button). In Chrome there is no such property, but there is another one that does the job – srcDocument.activeElement. This also points to the submit button of the form. Internet Explorer on the other hand does not have any of these shortcuts, so I had to use the long reference event.currentTarget.document.activeElement.

So what I did was add a data-download attribute to the form and check for it in the onbeforeunload handler like this:

window.onbeforeunload = function (e) {
  var target = e.currentTarget.document.activeElement;
  if ( target.form ) {
    target = target.form;
  }
  if ( ('getAttribute' in target) && 
       (target.getAttribute('download') != undefined || 
        target.getAttribute('data-download') != undefined) ) {
    return;
  }
  // Show loading spinner or do whatever you need to.
};

The above code does two things – polyfill browsers that don’t support the download attribute and apply similar logic to forms. It works in IE 9, 10, 11 and Edge, latest Firefox and Chrome.

Responsive header images with WordPress

от Гонзо
лиценз CC BY-NC-SA

Големите картинки, заемащи почти целия екран от доста време са на мода, но освен да впечатляват потребителя, те могат и доста да го изнервят докато чака да се заредят. Проблемът става съвсем явен когато потребителя разглежда сайта на екрана на телефона си, особено ако на мястото на което се намира няма 3G покритие. За решаването на този проблем на помощ идват отзивчивите картинки.

Годината е вече 2016та, най-популярните браузъри от доста време поддържат srcset, sizes и picture, така че няма причина да не ги използваме. А с новата версия 4.4 на WordPres това е още по-лесно. Не че с предишните беше трудно, просто трябваше да се инсталира разширението RIGC Responsive Images.

И така, постановката е проста, в горната част на сайта, под главата или като част от нея, искаме да поставим голяма картинка, която да заема цялата ширина на екрана. Освен това сайтът ще е с отзивчив дизайн, все пак е 2016 година вече. До тук нищо сложно, но като погледнем картинката с пропорции 16:9 на екрана на телефона в портретен режим и виждаме, че нещо е е така. Тогава сигурно бихме искали картинката също да е в портретен режим или поне да отива към квадрат, може би да увеличим основния елемент в нея. За да решат този проблем, хората от Responsive Images Comunity Group измислиха елемента picture.

Веднага ви давам пример:

<picture>
   <source media="(min-width: 45em)" srcset="large.jpg">
   <source media="(min-width: 32em)" srcset="med.jpg">
   <img src="small.jpg" alt="">
 </picture>

Браузърите, които поддържат picture, ще заредят първия елемент source, в чийто атрибут media е посочена медийна заявка, отговаряща на устройството на потребителя. Браузърите, които не поддържат picture просто ще покажат елемента img.

За да направим заглавната картинка в WordPress да използва елемента picture първо трябва да накараме WordPres да създава варианти на картинката с необходимите ни размери:

function image_sizes() {
   add_image_size( 'image-big', 1920, 1080, true );
   add_image_size( 'image-medium', 1024, 768, true );
   add_image_size( 'image-small', 480, 480, true );
 }
 add_action( 'after_setup_theme', 'image_sizes' );

След това идва интересната част. Тук предполагам, че темата вече поддържа custom-header. В предишните версии на WordPress заглавната картинка се показва обикновено с функцията header_image(). Но в най-добрия случай с тази функция ще получите img елемент с атрибути srcset и sizes, което не е това, което искаме. За да можем да използваме дефинираните от нас размери на картинки първо трябва да вземем ID на картинката и след това да използваме wp_get_attachment_image_src() за искания размер. Ако търсите начин да вземете ID на заглавната картинка в codex-а, няма да намерите (поне аз не успях), но един преглед на кода на WordPress веднага ще ви посочи get_custom_header() като решение на проблема. Накрая получаваме това:

<?php
 $header = get_custom_header();
 ?>
 <picture>
 <?php
 $src = wp_get_attachment_image_src()( $header->ID, 'image-big' )[0];
 ?>
 <source media="(min-width: 1024px)" src="<?php echo esc_attr($srcset); ?> />
 <?php
 $src = wp_get_attachment_image_src()( $header->ID, 'image-medium' )[0];
 ?>
 <source media="(min-width: 480px)" src="<?php echo esc_attr($srcset); ?> />
 <?php
 $src = wp_get_attachment_image_src()( $header->ID, 'image-small' )[0];
 ?>
 <img src="<?php echo esc_attr($src); ?>" />
 </picture>

Това е добре, но има един недостатък – една и съща картинка се сервира при доста голям диапазон от размери на екрана и съответно в доста случаи ще натовари клиента с излишен трафик и браузъра с по-тежко преоразмеряване на голяма картинка. Решението е на всеки source елемент вместо една картинка със src да подадем набор от размери чрез srcset. Тук на помощ идва функцията wp_get_attachment_image_srcset, която е нова в WordPress 4.4, в по-ранни версии може да се използва чрез разширението RICG Responsive Images. Тя връща srcset атрибут със всички дефинирани размери картинки със същата пропорция като подадения като втори параметър размер.

<?php
 $header = get_custom_header();
 ?>
 <picture>
 <?php
 $srcset_value = wp_get_attachment_image_srcset( $attachment_id, 'header-image-small' );
 $srcset = $srcset_value ? ' srcset="' . esc_attr( $srcset_value ) . '"' : '';
 ?>
 <source media="(min-width: 1024px)" <?php echo $srcset; ?> />
 <?php
 $srcset_value = wp_get_attachment_image_srcset( $attachment_id, 'wide-big' );
 $srcset = $srcset_value ? ' srcset="' . esc_attr( $srcset_value ) . '"' : '';
 ?>
 <source media="(min-width: 600px)" <?php echo $srcset; ?> />
 <?php
 $srcset_value = wp_get_attachment_image_srcset( $attachment_id, 'square-medium' );
 $srcset = $srcset_value ? ' srcset="' . esc_attr( $srcset_value ) . '"' : '';
 ?>
 <source <?php echo $srcset; ?> />
 <img src="<?php echo esc_attr(wp_get_attachment_image_src($attachment_id, 'header-image-base', false)[0]); ?>" />
 </picture>

И така получаваме картинка в главата, която наистина реагира на размера на екрана, с още малко усилия може да подавате различни картинки за различна ориентация при една и съща ширина на екрана. И при това без да изразходвате безплатните мегабайти от плана на потребителя.

Малко полезни разширения

Вече споменах RIGC Responsive Images, официалното разширение добавящо поддръжка за отзивчиви картинки в WordPress. От версия 4.4 вече е част от самия WordPress, но можете да го използвате, за да активирате лесно picturefill или заради добавянето на подобрена компресия на картинките.

Докато работите върху сайта и се опитвате да прецените какви точно пропорции на картинките ще подхождат на различните екрани и какви размери картинки да генерирате, неизбежно ще ви се наложи да променяте набора от размери, които искате WordPress да генерира. Тогава на помощ идват разни разширения, които регенерират различните размери картинки. След няколко проби аз се спрях на Image Regenerate & Select Crop.

Когато започнете да качвате картинки бързо ще забележите, че автоматичното оразмеряване не винаги работи добре и често ще имате нужда ръчно да отрежете картинка така, че да се вижда най-добре дадена част от нея. Тук на помощ идва Manual Image Crop – това разширение ви позволява ръчно да изберете как да се отреже всеки регистриран размер на картинката.

2676

от Ясен Праматаров
лиценз CC BY

Не успявам да остана достатъчно дълго насаме с празния екран, за да пиша. А имам за какво да разказвам. Пътувания през лятото, училище, работа… и уволнение… и пак работа, но за себе си. Много неща, но трудно започвам – все няма време или ако има време, нямам сили и спя. За да не е само мрънкане това започване, ще кажа, че мобилните програми – конкретно за Android – са готино предизвикателство. Много дълго отлагано, но за всяко нещо си идва времето. Реших и стратегия за учене – пиша това, което ми липсва на момента. Или го има, но е несвободно, не е във f-droid… другояче казано, пак ми липсва.

Етап две – приключване на сагата ми с търсене на нов лаптоп. Старият е на пределна възраст, но на почти никой нов не му харесвам копчетата. Има някои модели с прилични клавиатури – без омразния цифров блок, с групирани F-ове и с разположение на специални клавиши, с каквото съм свикнал. Но пък са все със слаби процесори внесените бройки… Трудно е, но скоро ще трябва да реша.