<feed xmlns="http://www.w3.org/2005/Atom"><title>Sealcode.org</title><id>https://hub.sealcode.org/phame/blog/feed/2/</id><link rel="self" type="application/atom+xml" href="https://hub.sealcode.org/phame/blog/feed/2/" /><link rel="alternate" type="text/html" href="https://hub.sealcode.org/phame/blog/view/2/" title="Sealcode.org" /><updated>2023-08-29T19:28:18+02:00</updated><subtitle>Porady programistyczne i nowinki ze świata IT/webdev</subtitle><entry><title>Avoiding race conditions when caching async stuff</title><link href="http://blog.sealcode.org/post/31/avoiding_race_conditions_when_caching_async_stuff/" /><id>https://hub.sealcode.org/phame/post/view/31/</id><author><name>kuba-orlik (Kuba Orlik)</name></author><published>2023-08-24T20:13:44+02:00</published><updated>2023-08-29T19:28:18+02:00</updated><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>Let&#039;s assume you have a function that&#039;s expensive to compute:</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">expensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{...}</span></pre></div>

<p>It takes one string and does something cpu-expensive with it that turns it into another string. It&#039;s a synchronous function, so it <strong>blocks the main thread for the entirety of its execution</strong>. We want to avoid executing it twice for the same value, so we devise a simple cache:</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">cache</span><span class="o">:</span><span class="w"> </span><span class="kt">Record</span><span class="o">&lt;</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">string</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{}</span></pre></div>

<p>We can now wrap the function to create a &quot;cached&quot; version of it:</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><div class="remarkup-code-header">invalid async cache</div><pre class="remarkup-code"><span></span><span class="kd">function</span><span class="w"> </span><span class="nx">cachedExpensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="o">:</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">{</span>
<span class="w">   </span><span class="k">if</span><span class="p">(</span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]){</span>
<span class="w">       </span><span class="k">return</span><span class="w"> </span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span>
<span class="w">   </span><span class="p">}</span>
<span class="w">   </span><span class="kd">const</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">expensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span>
<span class="w">   </span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">result</span><span class="p">;</span>
<span class="w">   </span><span class="k">return</span><span class="w"> </span><span class="nx">result</span><span class="p">;</span>
<span class="p">}</span></pre></div>

<p>The way it works is simple - if the value is already in the cache, return that value. Otherwise, calculate that value using the expensive function, save it in the cache and return it.</p>

<p>This way, the <tt class="remarkup-monospaced">cachedExpensiveFunction</tt> always returns the correct value - but it&#039;s going to be considerably faster when executed with inputs that have already been &quot;seen&quot; before.</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">cachedExpensiveFunction</span><span class="p">(</span><span class="s2">&quot;somestring&quot;</span><span class="p">))</span><span class="w">        </span><span class="c1">// returns the value slowly</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">cachedExpensiveFunction</span><span class="p">(</span><span class="s2">&quot;some-other-string&quot;</span><span class="p">))</span><span class="w"> </span><span class="c1">// returns the value slowly</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">cachedExpensiveFunction</span><span class="p">(</span><span class="s2">&quot;somestring&quot;</span><span class="p">))</span><span class="w">        </span><span class="c1">// returns the value fast!</span></pre></div>

<p>But what happens when the <tt class="remarkup-monospaced">expensiveFunction</tt> is asynchronous?</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">asyncExpensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="kt">string</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{...}</span></pre></div>

<p>Things are a little bit more complex now, and the naive aproach won&#039;t work</p>

<h3 class="remarkup-header">The naive approach that doesn&#039;t work</h3>

<p>One might think that just turning the cache function into async/await might resolve the issue like so:</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">asyncCachedExpensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="o">:</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="kt">string</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">  </span><span class="c1">// let&#39;s add `async` here...</span>
<span class="w">   </span><span class="k">if</span><span class="p">(</span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]){</span>
<span class="w">       </span><span class="k">return</span><span class="w"> </span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span>
<span class="w">   </span><span class="p">}</span>
<span class="w">   </span><span class="kd">const</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">expensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span><span class="w"> </span><span class="c1">// and `await` here!</span>
<span class="w">   </span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">result</span><span class="p">;</span>
<span class="w">   </span><span class="k">return</span><span class="w"> </span><span class="nx">result</span><span class="p">;</span>
<span class="p">}</span></pre></div>

<p>Unfortunately, this is not enough. The above implementation does not prevent calling the <tt class="remarkup-monospaced">asyncExpensiveFunction</tt> multiple times with the same input!</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="k">await</span><span class="w"> </span><span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
<span class="w">  </span><span class="nx">asyncCachedExpensiveFunction</span><span class="p">(</span><span class="s2">&quot;somestring&quot;</span><span class="p">),</span>
<span class="w">  </span><span class="nx">asyncCachedExpensiveFunction</span><span class="p">(</span><span class="s2">&quot;somestring&quot;</span><span class="p">),</span>
<span class="p">]);</span></pre></div>

<p>With the above setup, the <tt class="remarkup-monospaced">asyncExpensiveFunction</tt> <strong>will be called twice</strong>. So our cache doesn&#039;t work. But why?</p>

<p>Both of the calls for <tt class="remarkup-monospaced">asyncExpensiveFunction</tt> are executed &quot;in parallel&quot;. This means that when each of the calls gets to the <tt class="remarkup-monospaced">if(cache[input])</tt> part, nothing is there in the cache, so they decide to calculate the value by running the expensive function. The first function call that finishes writes the result to cache, which is promptly overwriten by the second function call.</p>

<p>This is the most problematic part:</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="kd">const</span><span class="w"> </span><span class="nx">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">expensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span><span class="w"> </span><span class="c1">// and `await` here!</span>
<span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">result</span><span class="p">;</span></pre></div>

<p>Look at that <tt class="remarkup-monospaced">await</tt> here. When we run <tt class="remarkup-monospaced">await</tt>, we tell JS to stop current execution and only resume it after the promise following the <tt class="remarkup-monospaced">await</tt> keyword is resolved or rejected. While the current execution is stopped, JS is free to execute any other function that&#039;s queued up - in this case that would be the second call to <tt class="remarkup-monospaced">asyncCachedexpensivefunction</tt>. We didn&#039;t write to <tt class="remarkup-monospaced">cache[input]</tt> yet - it&#039;s empty, so the second call will not use the cache.</p>

<p><strong>The root of the problem is the <tt class="remarkup-monospaced">await</tt> between running the <tt class="remarkup-monospaced">expensiveFunction</tt> and writing to cache</strong>.</p>

<h3 class="remarkup-header">Slightly less intuitive solution that *does* work</h3>

<p>To avoid the problem described above, we need to write to the cache *immediately*. Here&#039;s where Promises come in handy. We can avoid the issue described above by writing to the cache not *after* the expensive function has been called, but *before*. But what do we write to the cache when we don&#039;t yet have the result calculated?</p>

<p>The trick is to write a Promise to the cache. We can store a yet-unresolved Promise in a variable and write *that* to the cache.</p>

<div class="remarkup-code-block" data-code-lang="ts" data-sigil="remarkup-code-block"><div class="remarkup-code-header">proper async cache</div><pre class="remarkup-code"><span></span>
<span class="kd">const</span><span class="w"> </span><span class="nx">cache</span><span class="o">:</span><span class="w"> </span><span class="kt">Record</span><span class="o">&lt;</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="kt">string</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{}</span>

<span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">asyncCachedExpensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="o">:</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="kt">string</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"> </span>
<span class="w">   </span><span class="k">if</span><span class="p">(</span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]){</span>
<span class="w">       </span><span class="k">return</span><span class="w"> </span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span>
<span class="w">   </span><span class="p">}</span>
<span class="w">   </span><span class="kd">const</span><span class="w"> </span><span class="nx">resultPromise</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">expensiveFunction</span><span class="p">(</span><span class="nx">input</span><span class="p">);</span><span class="w"> </span><span class="c1">// no `await here!`</span>
<span class="w">   </span><span class="nx">cache</span><span class="p">[</span><span class="nx">input</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">resultPromise</span><span class="p">;</span>
<span class="w">   </span><span class="k">return</span><span class="w"> </span><span class="nx">resultPromise</span><span class="p">;</span>
<span class="p">}</span></pre></div>

<p>This way the first function call to execute stores the promise, and the second one to execute (remember, JS can *really* only one function at a time) already sees that there&#039;s a promise stored in the cache and returns that promise instead of creating a new one.</p>

<p>The clue intution to gather here is that a Promise can be moved around as any regular variable, and it can be returned, and awaited multiple times, with many time breaks in between.</p></div></content></entry><entry><title>Responsywne obrazki w HTML</title><link href="http://blog.sealcode.org/post/12/responsywne_obrazki_w_html/" /><id>https://hub.sealcode.org/phame/post/view/12/</id><author><name>kuba-orlik (Kuba Orlik)</name></author><published>2018-12-04T12:49:11+01:00</published><updated>2018-12-06T11:12:11+01:00</updated><content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><p>HTML umożliwia nam od pewnego czasu tworzenie responsywnych obrazków. W tym artykule opiszę pokrótce, jak z nich skutecznie korzystać.</p>

<h2 class="remarkup-header">Problem</h2>

<h3 class="remarkup-header">Piksel pikselowi nierówny</h3>

<p>Za starych &quot;dobrych&quot; czasów wszystkie ekrany miały zbliżone rozmiary i rozdzielczość. Mieliśmy 640x480, 800x600, 1024x768. Dzięki takiej jednolitości standardem stało się używanie jednostki <tt class="remarkup-monospaced">px</tt> do określania rozmiarów elementów stron WWW w CSS.</p>

<p>Gdy rozdzielczości ekranów desktopowych zaczęła rosnąć a na rynku zaczęły pojawiać się urządzenia mobilne o względnie małych ekranach nagle okazało się, że jednostka <tt class="remarkup-monospaced">px</tt> jest problematyczna, bo tekst o rozmiarze <tt class="remarkup-monospaced">20px</tt> będzie za duży na smartfonie, a za mały na desktopowym ekranie 4k.</p>

<p>Dlatego przeglądarki przestały traktować jednostkę <tt class="remarkup-monospaced">px</tt> dosłownie - teraz ustawiając wysokość jakiegoś elementu na <tt class="remarkup-monospaced">10px</tt> <strong>nie mamy gwarancji, że będzie to odpowiadało dokładnie 10-ciu fizycznym pikselom na ekranie</strong>. Jeżeli mamy mały ekran o względnie dużej rozdzielczości, to te 10 &quot;pikseli&quot; tak naprawdę może zająć 20 lub nawet 40 faktycznych, fizycznych pikseli.</p>

<p>Przeglądarki na podstawie różnych parametrów określają ilość pikseli fizycznych na jeden piksel css-owy (w Firefoksie można ją skonfigurować pod flagą <tt class="remarkup-monospaced">layout.css.devPixelsPerPx</tt>). Im większa ta proporcja, tym większe na naszym ekranie będą elementy wyświetlane przez przeglądarkę.</p>

<p><span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/7nrzlmpqkxgxrntvvcat/PHID-FILE-goqxjr4q7wt4nlnwjkfb/about%3Aconfig_-_Firefox_Developer_Edition_183.png" class="phabricator-remarkup-embed-image" data-sigil="lightboxable" data-meta="0_0"><img src="https://cdn.sealco.de/file/data/5gk6njvuzcvekowetas6/PHID-FILE-3sgy46xeccv74dwx27fa/preview-about%3Aconfig_-_Firefox_Developer_Edition_183.png" width="220" height="215.47945205479" alt="about:config - Firefox Developer Edition_183.png (730×715 px, 43 KB)" /></a></span> <span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/enbprzv4qy6t4yyc672s/PHID-FILE-cfznkkq5rijb46wbko7b/about%3Aconfig_-_Firefox_Developer_Edition_184.png" class="phabricator-remarkup-embed-image" data-sigil="lightboxable" data-meta="0_1"><img src="https://cdn.sealco.de/file/data/o34vmarg55lma7kg5zsb/PHID-FILE-v5vb5ds7mesfji3hkahu/preview-about%3Aconfig_-_Firefox_Developer_Edition_184.png" width="220" height="215.47945205479" alt="about:config - Firefox Developer Edition_184.png (730×715 px, 47 KB)" /></a></span></p>

<p>Dzięki temu użytkownik konfigurując sobie przeglądarkę (lub system operacyjny) może skalibrować ustawienia tak, aby tekst nie był za mały ani za duży.</p>

<p>Ale <tt class="remarkup-monospaced">1px</tt> w css nie zawsze jest równy <tt class="remarkup-monospaced">1px</tt> na ekranie.</p>

<p><strong>O ile tekst można łatwo skalować, o tyle zdjęcia są bardziej problematyczne.</strong></p>

<h3 class="remarkup-header">Różne gęstości pikseli</h3>

<p>Jeżeli stworzymy sobie grafikę o faktycznym rozmiarze 100px na 100px i osadzimy ją w html:</p>

<div class="remarkup-code-block" data-code-lang="html" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="p">&lt;</span><span class="nt">img</span> <span class="na">src</span><span class="o">=</span><span class="s">&quot;100.png&quot;</span> <span class="na">width</span><span class="o">=</span><span class="s">&quot;100&quot;</span> <span class="na">height</span><span class="o">=</span><span class="s">&quot;100&quot;</span><span class="p">/&gt;</span></pre></div>

<p>To na ekranie o tradycyjnej gęstości pikseli wyświetli się dobrze:</p>

<p><div class="phabricator-remarkup-embed-layout-left"><a href="https://cdn.sealco.de/file/data/mp7pm4dnxfkd6oo6e4g7/PHID-FILE-g7t3g73ezjzcm2yg2usw/image.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_2"><img src="https://cdn.sealco.de/file/data/mp7pm4dnxfkd6oo6e4g7/PHID-FILE-g7t3g73ezjzcm2yg2usw/image.png" height="109" width="107" loading="lazy" alt="image.png (107×109 px, 2 KB)" /></a></div></p>

<p>ale na ekranie o 2x gęstości jest już gorzej:</p>

<p><div class="phabricator-remarkup-embed-layout-left"><a href="https://cdn.sealco.de/file/data/52e3m7bhbcivwrgcehgn/PHID-FILE-daadzeq37gjxru3hqrcb/scaled.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_3"><img src="https://cdn.sealco.de/file/data/52e3m7bhbcivwrgcehgn/PHID-FILE-daadzeq37gjxru3hqrcb/scaled.png" height="110" width="112" loading="lazy" alt="scaled.png (112×110 px, 3 KB)" /></a></div></p>

<p>Widzimy, że zdjęcie jest nieostre - jest tak dlatego, że oryginalny plik (o rozdzielczości 100px na 100px) musiał być przez przeglądarkę  <em>rozciągnięty</em> do 200x200 <em>fizycznych</em> pikseli, aby je wyświetlić zgodnie z zaprojektowanym layoutem. A skalowanie obrazka zawsze będzie się wiązało z pewną utratą jakości (zwłaszcza, jeżeli rozciągamy zdjęcie, a nie kurczymy).</p>

<p>Jednym z rozwiązań może być wygenerowanie pliku graficznego o rozdzielczości 200x200 i używanie go zamiast pliku 100x100. O ile pomoże to zachować ostrość zdjęć, o tyle spowoduje to kolejny problem - strona staje się &quot;cięższa&quot;, bo przeglądarka musi pobrać znacznie więcej danych aby stronę wyświetlić. Do tego dochodzi fakt, że użytkownicy o tradycyjnej rozdzielczości ekranu musieliby wtedy pobierać pliki graficzne o dużej rozdzielczości tylko po to, aby wyświetlać je w mniejszej rozdzielczości - marnując pasmo pobierania na coś, czego nie mogą zobaczyć.</p>

<h3 class="remarkup-header">Różne rozmiary ekranu, responsywny layout</h3>

<p>Olbrzymie rozdzielczości ekranu nie są jedyną potencjalną przyczyną ładowania zbyt dużych plików graficznych. Responsywne layouty, które dostosowują się do dowolnej szerokości ekranu stwarzają kolejne komplikacje.</p>

<p>Załóżmy, że tworzymy stronę, na której jest widoczne duże zdjęcie - np. post na blogu z coverową grafiką. Tak prezentuje się na desktopie i mobilce:</p>

<p><span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/iykskrmpxmz6ttevl7ym/PHID-FILE-h4j45dgqdohbeow5vng4/blog.svg.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_4"><img src="https://cdn.sealco.de/file/data/iykskrmpxmz6ttevl7ym/PHID-FILE-h4j45dgqdohbeow5vng4/blog.svg.png" height="400" width="533" loading="lazy" alt="blog.svg.png (533×400 px, 17 KB)" /></a></span> <span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/o5t3jct7lcyyeitlwtuj/PHID-FILE-fajunompwmuyscuns7hb/blog-mobile.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_5"><img src="https://cdn.sealco.de/file/data/o5t3jct7lcyyeitlwtuj/PHID-FILE-fajunompwmuyscuns7hb/blog-mobile.png" height="400" width="533" loading="lazy" alt="blog-mobile.png (533×400 px, 15 KB)" /></a></span></p>

<p>Jak widać, grafika na mobilce jest znacznie mniejsza, niż na desktopie - i nie jest to w tym wypadku spowodowane zmianą gęstości pikseli, ale różnymi rozmiarami ekranu.</p>

<p>Nie zawsze jest tak, że na mobilkę będziemy wysyłać mniejsze zdjęcia niż na desktop - wszystko zależy od tego, jak ułożymy layout. Na przykład w layoucie opartym o siatkę elementów:</p>

<p><span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/2cmd6qmi44uoydhnyq7b/PHID-FILE-7oksytsehf4iaf5b27xt/grid-desktop.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_6"><img src="https://cdn.sealco.de/file/data/2cmd6qmi44uoydhnyq7b/PHID-FILE-7oksytsehf4iaf5b27xt/grid-desktop.png" height="400" width="533" loading="lazy" alt="grid-desktop.png (533×400 px, 17 KB)" /></a></span> <span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/g4nwpspc2cztnukxfk3q/PHID-FILE-ryj6vap52fwlvkrv5xzl/grid-mobile.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_7"><img src="https://cdn.sealco.de/file/data/g4nwpspc2cztnukxfk3q/PHID-FILE-ryj6vap52fwlvkrv5xzl/grid-mobile.png" height="400" width="533" loading="lazy" alt="grid-mobile.png (533×400 px, 12 KB)" /></a></span></p>

<p>Grafiki na mobilce są większe, niż na desktopie.</p>

<p>Dołóżmy do tego fakt, że różne telefony i desktopy mogą mieć różną gęstość pikseli i mamy niezłe 🍝, którego zwykłymi media-queries się nie rozplącze.</p>

<h2 class="remarkup-header">Rozwiązanie</h2>

<p>Na pomoc przychodzą <a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images" class="remarkup-link remarkup-link-ext" rel="noreferrer">responsywne obrazki</a>.</p>

<p>Dodano do tagu <tt class="remarkup-monospaced">img</tt> w HTML dwa nowe atrybuty: <tt class="remarkup-monospaced">srcset</tt> i <tt class="remarkup-monospaced">sizes</tt>. Możemy ich użyć aby deklaratywnie pomóc przeglądarce samodzielnie wybrać, jaki obrazek będzie najlepszy na danym urządzeniu w danym czasie, biorąc pod uwagę prędkość łącza, rozdzielczość ekranu, wielkość ekranu, gęstość pikseli, etc.</p>

<div class="remarkup-code-block" data-code-lang="html" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="p">&lt;</span><span class="nt">img</span>
	<span class="na">src</span><span class="o">=</span><span class="s">&quot;/images/artwork-100w.png&quot;</span>
	<span class="na">srcset</span><span class="o">=</span><span class="s">&quot;</span>
<span class="s">		/images/artwork-100w.png 100w,</span>
<span class="s">		/images/artwork-200w.png 200w,</span>
<span class="s">		/images/artwork-300w.png 300w,</span>
<span class="s">		/images/artwork-400w.png 400w</span>
<span class="s">	&quot;</span>
	<span class="na">sizes</span><span class="o">=</span><span class="s">&quot;100px&quot;</span>
<span class="p">/&gt;</span></pre></div>

<p>W tym przykładzie każdy z wymienionych obrazków (<tt class="remarkup-monospaced">artwork-100w.png</tt>, <tt class="remarkup-monospaced">artwork-200w.png</tt> itd) są osobnymi plikami umieszczonymi na serwerze i dostępnymi dla przeglądarki. W tym przykładzie możemy założyć, że każdy z tych plików jest tym samym zdjęciem, ale w różnych rozdzielczościach, wygenerowanych na podstawie oryginalnego, znacznie większego pliku graficznego.</p>

<p>W <tt class="remarkup-monospaced">src</tt> umieszczamy link do zdjęcia które ma się pojawiać w przeglądarkach które nie wspierają responsywnych obrazków.</p>

<p>W <tt class="remarkup-monospaced">srcset</tt> podajemy listę wszystkich plików, spośród których chcemy, aby przeglądarka mogła wybrać ten, który pobierze i wyświetli. Po każdym URL do pliku graficznego podajemy jego <em>szerokość</em> w pikselach. Czyli jeżeli dany link prowadzi do pliku o szerokości 100px, to po jego URL robimy spację i piszemy <tt class="remarkup-monospaced">100w</tt>. <tt class="remarkup-monospaced">w</tt> w tym wypadku jest jednostką określającą faktyczny rozmiar zdjęcia w pikselach. Warto zwrócić uwagę, że w powyższym przykładzie plik <tt class="remarkup-monospaced">100w</tt> jest podany w <tt class="remarkup-monospaced">srcset</tt> mimo, że znajduje się już w <tt class="remarkup-monospaced">src</tt>. Taka duplikacja jest potrzebna, aby ten plik także był brane pod uwagę przez przeglądarkę przy wybieraniu odpowiedniego rozmiaru.</p>

<p>W <tt class="remarkup-monospaced">sizes</tt> możemy podać jeden lub więcej (o tym później) rozmiarów grafiki w jednostkach css (podajemy tylko szerokość). Przeglądarka następnie na podstawie rozdzielczości ekranu i gęstości pikseli  przekonwertuje te jednostki css do ilości fizycznych pikseli potrzebnych do wyświetlenia grafiki (jednostka <tt class="remarkup-monospaced">w</tt>) i wybierze odpowiednie zdjęcie z <tt class="remarkup-monospaced">srcset</tt>. <strong>Obecność atrybutu <tt class="remarkup-monospaced">sizes</tt> jest konieczna, aby uzyskać efekt responsywnych obrazków</strong>.</p>

<h3 class="remarkup-header"><tt class="remarkup-monospaced">sizes</tt></h3>

<p>Po co jest atrybut <tt class="remarkup-monospaced">sizes</tt>? Przecież przeglądarka może sobie wywnioskować na podstawie załączonych do html-a css-ów jaki będzie rozmiar zdjęcia na ekranie i to na tej podstawie wybrać odpowiedni plik, prawda?</p>

<p><a href="https://www.youtube.com/watch?v=_24skr1DhxU" class="remarkup-link remarkup-link-ext" rel="noreferrer">Otóż nie</a>. Przeglądarka chce pobrać odpowiedni plik graficzny jak najszybciej, a nie dopiero jak załaduje się CSS i zakończy layout całego dokumentu. Dlatego system responsywnych obrazków został zrobiony tak, aby przeglądarka mogła przed wyrenderowaniem layoutu zdecydować, jaki plik zacząć pobierać. Potrzebuje do tego znać tylko rozdzielczość ekranu (lub rozmiar aktualnego okna przeglądarki), gęstość pikseli i informację o tym, jaki rozmiar będzie miało zdjęcie zależnie od rozmiaru ekranu. Dwie pierwsze dane przeglądarka jest w stanie zdobyć samodzielnie - trzecią z nich podajemy jej my, za pomocą atrybutu <tt class="remarkup-monospaced">sizes</tt>.</p>

<p>W <tt class="remarkup-monospaced">sizes</tt> używamy <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Media_features" class="remarkup-link remarkup-link-ext" rel="noreferrer">media conditions</a> (z reguły głównie <tt class="remarkup-monospaced">max-width</tt>) aby określić, jaką szerokość będzie miał dany obrazek na danej szerokości.</p>

<p>Pokażę to na przykładzie takiego layoutu, w którym zdjęcie zajmuje całą szerokość ekranu - chyba, że ekran jest bardzo szeroki, to wtedy zdjęcie jest wycentrowane i ma marginesy po bokach:</p>

<p><span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/ree6zlerm5fall6mhola/PHID-FILE-q5kszar2yifatww7dgyl/growing-mobile.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_8"><img src="https://cdn.sealco.de/file/data/ree6zlerm5fall6mhola/PHID-FILE-q5kszar2yifatww7dgyl/growing-mobile.png" height="400" width="228" loading="lazy" alt="growing-mobile.png (228×400 px, 10 KB)" /></a></span> <span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/o6ywvg7n53oknj7oo7ix/PHID-FILE-hzvbd5ywn5tddxuaq56r/growing-medium.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_9"><img src="https://cdn.sealco.de/file/data/o6ywvg7n53oknj7oo7ix/PHID-FILE-hzvbd5ywn5tddxuaq56r/growing-medium.png" height="400" width="450" loading="lazy" alt="growing-medium.png (450×400 px, 10 KB)" /></a></span> <span class="phabricator-remarkup-embed-layout-inline"><a href="https://cdn.sealco.de/file/data/ydt6r73jyst64au7kjmj/PHID-FILE-j53bpko2zfv7ads35vho/growing-desktop.png" class="phabricator-remarkup-embed-image-full" data-sigil="lightboxable" data-meta="0_10"><img src="https://cdn.sealco.de/file/data/ydt6r73jyst64au7kjmj/PHID-FILE-j53bpko2zfv7ads35vho/growing-desktop.png" height="400" width="758" loading="lazy" alt="growing-desktop.png (758×400 px, 11 KB)" /></a></span></p>

<p>Przy założeniu, że <tt class="remarkup-monospaced">900px</tt> to ta maksymalna szerokość zdjęcia (podana w css-owych pikselach), to nasz html może wyglądać tak:</p>

<div class="remarkup-code-block" data-code-lang="html" data-sigil="remarkup-code-block"><pre class="remarkup-code"><span></span><span class="p">&lt;</span><span class="nt">img</span>
  <span class="na">src</span><span class="o">=</span><span class="s">&quot;artwork-100asdfw.png&quot;</span>
  <span class="na">srcset</span><span class="o">=</span><span class="s">&quot;</span>
<span class="s">    artwork-100w.png   100w,</span>
<span class="s">    artwork-200w.png   200w,</span>
<span class="s">    artwork-500w.png   500w,</span>
<span class="s">    artwork-1000w.png 1000w,</span>
<span class="s">    artwork-1500w.png 1500w,</span>
<span class="s">    artwork-2000w.png 2000w</span>
<span class="s">  &quot;</span>
  <span class="na">sizes</span><span class="o">=</span><span class="s">&quot;(max-width: 900px) 100vw,</span>
<span class="s">           900px&quot;</span>
<span class="p">/&gt;</span></pre></div>

<p>Tutaj w <tt class="remarkup-monospaced">sizes</tt> mówimy <em>&quot;Jeżeli ekran ma mniej niż 900 css-owych pikseli, to zdjęcie zajmuje 100% jego szerokości. W przeciwnym wypadku zdjęcie ma 900 css-owych pikseli szerokości&quot;</em>. (<tt class="remarkup-monospaced">vw</tt> to jednostka css. <tt class="remarkup-monospaced">100vw</tt>= 100% szerokości ekranu. <tt class="remarkup-monospaced">50vw</tt> = 50% szerokości ekranu, itd)</p>

<p>Odpowiednie opisanie <tt class="remarkup-monospaced">sizes</tt> może być trochę kłopotliwe, i należy je zaktualizować gdy zmieniamy layout, ale obecnie jest to najlepsze rozwiązanie, pozwalające na optymalizację pasma i dostosowywanie się do różnych rozdzielczości ekranu przy wyświetlaniu obrazków.</p>

<h3 class="remarkup-header">Czy robię to dobrze?</h3>

<p>Jak już masz ustawione responsywne obrazki na swojej stronie, włącz podgląd zapytań HTTP w narzędziach deweloperskich i zobacz, która wersja zdjęcia jest pobierana. Możesz dodatkowo włączyć tryb responsywny i zmieniać szerokość ekranu i sprawdzić, czy przeglądarka dociąga mniejsze/większe wersje zdjęcia przy odpowiednich zmianach szerokości.</p></div></content></entry></feed>