A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 62KB

4 lat temu
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>When Responsive Images Get Ugly (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://codepen.io/Tigt/post/when-responsive-images-get-ugly">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. When Responsive Images Get Ugly (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://codepen.io/Tigt/post/when-responsive-images-get-ugly">Source originale du contenu</a></h3>
  445. <p>I don’t know about you, but I’m over the current crop of “How to use our new friends <code>&lt;picture&gt;</code> and <code>srcset</code>” tutorials. They show you some Baby’s First Markup which is nice for teaching purposes, but doesn’t prepare you for the ugliness that more… unusual applications require.</p>
  446. <p>I’ve encountered a few corner-cases and quirks to beware of and found a few of said unusual applications. Maybe they can help you.</p>
  447. <nav class="toc">
  448. <h2>Contents</h2>
  449. <ol>
  450. <li><a href="#some-ground-floor-stuff">Some ground floor stuff</a>
  451. <ol>
  452. <li><a href="#why-bother">Why bother?</a></li>
  453. <li><a href="#srcset-is-preferable">srcset is preferable</a></li>
  454. </ol>
  455. </li>
  456. <li><a href="#unintuitive-behavior-gotchas">Unintuitive behavior &amp; gotchas</a>
  457. <ol>
  458. <li><a href="#media-conditions-not-media-queries">Media conditions, NOT media queries</a></li>
  459. <li><a href="#troublesome-css-units">Troublesome CSS units</a></li>
  460. <li><a href="#sizes-will-affect-how-your-image-displays">sizes will affect how your image displays</a></li>
  461. </ol>
  462. </li>
  463. <li><a href="#media-aware-images">Media-aware images</a>
  464. <ol>
  465. <li><a href="#printer-friendly">Printer-friendly</a></li>
  466. <li><a href="#e-ink-friendly">E-ink-friendly</a></li>
  467. <li><a href="#night-mode-bright-sunlight">Night mode &amp; bright sunlight</a></li>
  468. </ol>
  469. </li>
  470. <li><a href="#the-deepest-backwards-compatibility-possible">The deepest backwards-compatibility possible</a></li>
  471. <li><a href="#height-and-width-constrained-srcset">Height and width-constrained srcset</a>
  472. <ol>
  473. <li><a href="#object-fit-cover">object-fit: cover</a></li>
  474. <li><a href="#object-fit-contain">object-fit: contain</a></li>
  475. </ol>
  476. </li>
  477. <li><a href="#responsive-bitmaps-inside-inline-svg">Responsive bitmaps inside inline SVG</a>
  478. <ol>
  479. <li><a href="#fixing-ie">Fixing IE</a></li>
  480. <li><a href="#building-the-sizes">Building the sizes</a></li>
  481. <li><a href="#making-things-worst-with-not-quite-entire-viewport-scaling">Making things worst with not-quite-entire-viewport scaling</a>
  482. <ol>
  483. <li><a href="#the-browser-viewport">The browser viewport</a></li>
  484. <li><a href="#the-svg-element">The <code>&lt;svg&gt;</code> element</a></li>
  485. <li><a href="#the-viewbox">The viewBox</a></li>
  486. <li><a href="#the-image">The <code>&lt;image&gt;</code></a></li>
  487. <li><a href="#assembling">Assembling</a></li>
  488. </ol>
  489. </li>
  490. </ol>
  491. </li>
  492. <li><a href="#thats-all-folks">That’s all, folks</a></li>
  493. </ol>
  494. </nav>
  495. <h2 id="some-ground-floor-stuff">Some ground floor stuff</h2>
  496. <p>I’m assuming you already have a basic grasp of responsive images. If not, I recommend <a href="http://blog.cloudfour.com/responsive-images-101-definitions/">Cloud Four’s introduction series</a>.</p>
  497. <h3 id="why-bother">Why bother?</h3>
  498. <p>If the ensuing code has you despairing that responsive images are more trouble than they’re worth, refer to this section to remember that <strong>overlarge images are painful,</strong> in a variety of ways:</p>
  499. <dl class="bulleted">
  500. <dt>Download size</dt>
  501. <dd>Doubling an image’s pixel density quadruples its area and can <em>sextuple</em> its filesize! Have a data plan? Not anymore you don’t.</dd>
  502. <dt>CPU load &amp; battery drain</dt>
  503. <dd>Images also need to be decoded. Shrinking ‘em with CSS? Now the browser needs to scale and resample, too. The difficulty of all three grows exponentially with image size.</dd>
  504. <dt>Memory usage</dt>
  505. <dd>Big photographs <a href="https://twitter.com/keithclarkcouk/status/605983582709010432">can cause juddering in a mobile browser all by themselves</a>. Resizing with CSS doesn’t help; the browser keeps the full thing around for zooming.</dd>
  506. </dl>
  507. <p>An oversized image wastes time, bandwidth, data, battery, and system resources. Even if you don’t use a polyfill, you should use responsive images for <a href="http://caniuse.com/#feat=srcset">the 56% (and growing!) of browsers that support them</a>; anything else would be irresponsible.</p>
  508. <h3 id="srcset-is-preferable"><code>srcset</code> is preferable</h3>
  509. <p><code>&lt;picture&gt;</code> is darling, but a plain <code>&lt;img srcset&gt;</code> is more flexible: bandwidth-saving modes, browser heuristics, and user choices. It’s also more future-friendly; <code>&lt;picture&gt;</code> is only as good as your media queries. <code>srcset</code> is the smart choice.</p>
  510. <h2 id="unintuitive-behavior-gotchas">Unintuitive behavior &amp; gotchas</h2>
  511. <p>Like everything else on the front-end, responsive images are fraught with edge cases and unforeseen applications. I’ve collected a couple things that have tripped me up, in the hopes that they won’t do the same to you.</p>
  512. <p>The most important <em>and</em> trickiest part of responsive images is the <code>sizes</code> attribute. If it doesn’t tell the browser <em>exactly</em> how big the image is going to be, you risk either a scaled-up mess or a scaled-down bloat. So naturally the <code>sizes</code> syntax is error-prone.</p>
  513. <p>In Standardese, the <code>sizes</code> attribute takes a comma-separated list of <code>&lt;source-size-value&gt;</code>s, which break down into a <b>media condition</b> (not a media query!) and a <b>length</b>, like this:</p>
  514. <pre>
  515. <code>sizes="(media: condition) length,
  516. (media: condition) length,
  517. (media: condition) length"
  518. </code>
  519. </pre>
  520. <p>The length is the width of the image if the media condition is true. It accepts a value, like <code>700px</code>, or a <code>calc()</code> expression.</p>
  521. <p>Media conditions are almost media queries. The spec <a href="http://www.w3.org/TR/mediaqueries-4/#typedef-media-condition">codifies them in a formal CSS grammar</a>, but for us <em>mere mortals</em> the rules are:</p>
  522. <ul>
  523. <li>You cannot use media types (<code>screen</code>, <code>print</code>, <code>tv</code>, etc.)</li>
  524. <li>Use any <code>(feature: value)</code> pairs you like.</li>
  525. <li>You can chain them with the keywords <code>and</code>, <code>or</code>, and <code>not</code>. Do <strong>not</strong> use commas as a substitute for <code>or</code>.</li>
  526. <li>If using the <code>or</code> or <code>not</code> keywords, you must wrap the entire media condition in parentheses.</li>
  527. <li>If empty, it always evaluates to true.</li>
  528. </ul>
  529. <p>But I've always preferred examples. Much simpler to get the shape of things from them than trying to string together a buncha rules.</p>
  530. <figure>
  531. <figcaption>Some valid media conditions:</figcaption>
  532. <ul>
  533. <li><code>(min-width: 500px)</code>
  534. </li><li><code>(max-width: 300px) and (orientation: portrait)</code>
  535. </li><li><code>((min-height: calc(50vw - 100px)) or (light-level: dim))</code>
  536. </li><li><code> </code> (Yep, an empty M.C. is totes O.K.)
  537. </li>
  538. </ul>
  539. </figure>
  540. <figure>
  541. <figcaption>Some valid lengths:</figcaption>
  542. <ul>
  543. <li><code>200px</code>
  544. </li><li><code>40em</code>
  545. </li><li><code>calc(40rem - 10vw)</code>
  546. </li><li><code>78.95vmax</code>
  547. </li>
  548. </ul>
  549. </figure>
  550. <figure>
  551. <figcaption>Some valid <code>&lt;source-size-value&gt;</code>s:</figcaption>
  552. <ul>
  553. <li><code>(min-width: 500px) 200px</code>
  554. </li><li><code>(max-width: 300px) and (orientation: portrait) 40em</code>
  555. </li><li><code>((min-height: calc(50vw - 100px)) or (light-level: dim)) calc(40rem - 10vw)</code>
  556. </li><li><code>78.95vmax</code>
  557. </li>
  558. </ul>
  559. </figure>
  560. <p>Ugly? Sure. Powerful? You bet.</p>
  561. <p>Now, because this is web development, and <em>nothing can ever be easy</em>, browsers aren’t required to have fully implemented media condition grammar before they implement responsive images. You may need backup conditions to work around that:</p>
  562. <pre>
  563. <code>"((min-height: calc(50vw - 100px)) or (light-level: dim)) 500px,
  564. (min-height: calc(50vw - 100px) 500px,
  565. (light-level: dim) 500px"
  566. </code>
  567. </pre>
  568. <p>As always, test, test, test. I recommend <a href="http://placehold.it/">placehold.it</a> for images that explicitly indicate their widths; lessens ambiguity and saves you from having to check the <code>currentSrc</code> every time.</p>
  569. <h3 id="troublesome-css-units">Troublesome CSS units</h3>
  570. <p>
  571. <a href="http://www.w3.org/TR/html51/semantics.html#source-size-value">So sayeth the spec:</a>
  572. </p>
  573. <blockquote>
  574. <p><b>Note:</b> Percentages are not allowed in a <code>&lt;source-size-value&gt;</code>, to avoid confusion about what it would be relative to. The vw unit can be used for sizes relative to the viewport width.</p>
  575. </blockquote>
  576. <p>The browser image preloader wants the proper URL to request ASAP. It’s not waiting around for your CSS to download and parse, it needs units it can use <em>now.</em> And since the <code>%</code> unit only works if you know how big an element’s parents are, the CSS is required to calculate it.</p>
  577. <p><code>rem</code> and <code>em</code> <em>are</em> supported, but be careful. They’re resolved <a href="http://ircbot.responsiveimages.org/bot/log/respimg/2015-06-04#T132199">like they are inside media queries</a>: equal to the user’s default <code>font-size</code>. (Which is often 16px, but not always.) If you’re respecting that default, you should be good to go.</p>
  578. <p>But if you’re doing something like <code>html {font-size: 10px}</code> to make the math easier, well, don’t. That <a href="https://vasilis.nl/nerd/using-pixels-polite/">doesn’t respect the user</a>, and it prevents you from using <a href="http://blog.cloudfour.com/the-ems-have-it-proportional-media-queries-ftw/">the power of <code>em</code>s in media queries</a>.</p>
  579. <p>You also can’t do <code>html {font-size: 62.5%}</code> if you want to use <code>em</code> in <code>sizes</code>. What with preprocessors nowadays you don’t need to, and <a href="http://www.filamentgroup.com/lab/how-we-learned-to-leave-body-font-size-alone.html">embracing the default user <code>font-size</code> is a good practice</a>. Prevents a lot of <a href="https://vasilis.nl/nerd/weird-62-5-antipattern/">inheritance woes</a>.</p>
  580. <p>As for why you’d size an image with <code>em</code>in the first place…</p>
  581. <ul>
  582. <li>If your breakpoints are using <code>em</code>, you'll reflect them in your media conditions.</li>
  583. <li>If you’re sizing your text container with <code>em</code>s for <a href="http://maxdesign.com.au/articles/ideal-line-length-in-ems/">the perfect line length</a>, and images inside it are <code>max-width: 100%</code>, they can end up sized in <code>em</code>s too.</li>
  584. </ul>
  585. <p>As for those weirdo units, like <code>ch</code> or <code>ex</code> or <code>pc</code>… If you use them, Tim Berners-Lee calls Håkon W. Lie, who calls Bert Bos, and <em>he</em> calls the authorities.</p>
  586. <h3 id="sizes-will-affect-how-your-image-displays"><code>sizes</code> <em>will</em> affect how your image displays</h3>
  587. <p><code>sizes</code> appears to be just hints about how your CSS scales the image, but it actually changes how the image will be displayed all on its own. Observe:</p>
  588. <p/><p data-slug-hash="XbRaVP" data-default-tab="result" data-height="900" data-theme-id="14446" class="codepen"/>
  589. <p>If your screen is big, you can see the last image is way enormous. It doesn't have a <code>sizes</code> attribute, and browsers assume <code>sizes="100vw"</code> when that happens. Widespread misuse of this default is why <a href="http://alistapart.com/blog/post/article-update-dont-rely-on-default-sizes">it’s no longer valid to omit <code>sizes</code> on a responsive image</a>.</p>
  590. <p>If you aren’t using CSS to explicitly size the image at all times, like if you’ve got <code>img { max-width: 100% }</code> and not all your images will be as big as their containers, be careful and test to make sure your <code>sizes</code> attribute isn’t running amok. Browsers treat the length of the chosen <code>&lt;source-size-value&gt;</code> like <code>&lt;img width&gt;</code>: if you don’t override it with CSS, that’s how wide it will be.</p>
  591. <p>Using <code>&lt;picture&gt;</code> and the full power of the <code>media</code> attribute, you can make especially important images (logos, diagrams, etc.) adapt best to device capabilities and circumstances.</p>
  592. <h3 id="printer-friendly">Printer-friendly</h3>
  593. <pre>
  594. <code class="html">&lt;picture&gt;
  595. &lt;source srcset="logo-printer-friendly.svg" type="image/svg+xml" media="print, (monochrome: 1)"&gt;
  596. &lt;img src="logo.png" alt="Butt-Touchers Incorporated"&gt;
  597. &lt;/picture&gt;
  598. </code>
  599. </pre>
  600. <p>You’ve probably seen print stylesheet tutorials that <code>display: none</code> images and substitute printer-friendly versions as background images. Since not all browsers print with background images &amp; colors on (at least, not by default), this version is much more robust. For important content images, it’s <em>way</em> better.</p>
  601. <p>I also specify the image for devices that can’t display colors (<code>monochrome: 1</code>), because hey, we already made a B&amp;W version for saving ink.</p>
  602. <h3 id="e-ink-friendly">E-ink-friendly</h3>
  603. <p>A device that qualifies for <code>update-frequency: slow</code> (mostly e-Ink devices) probably wouldn’t play GIFs, but if it did, it would look very muddy and blurry as it would struggle to keep up.</p>
  604. <p>In either case, it would be much nicer to display a still frame that’s representative of the GIF instead. (As opposed to showing the first frame, which is an often unsuitable default.) So that’s what we do here, and hey, print doesn't have an update frequency, right?:</p>
  605. <pre>
  606. <code class="html">&lt;picture&gt;
  607. &lt;source srcset="reaction-frame.png" media="print, (update-frequency: slow)"&gt;
  608. &lt;img src="reaction.gif" alt="Say whaaaaat?"&gt;
  609. &lt;/picture&gt;
  610. </code>
  611. </pre>
  612. <h3 id="night-mode-bright-sunlight">Night mode &amp; bright sunlight</h3>
  613. <pre>
  614. <code class="html">&lt;picture&gt;
  615. &lt;source srcset="floorplan-dark.svg" media="(light-level: dim)"&gt;
  616. &lt;source srcset="floorplan-hicontrast.svg" media="(light-level: washed)"&gt;
  617. &lt;img src="floorplan.png" alt="The plans for how we'll renovate the apartment." longdesc="#our-plans" aria-describedby="our-plans"&gt;
  618. &lt;/picture&gt;
  619. </code>
  620. </pre>
  621. <p>Using light level media queries, we can adapt images to be higher contrast and visible in strong sunlight conditions (<code>washed</code>), or invert the colors to be less distracting in the dark (<code>dim</code>).</p>
  622. <p>These aren’t features you should scramble to implement, but keep them in your back pocket. The print-friendly one is useful today, <code>light-level</code> is gaining implementation (Firefox already supports it), and cheapo e-Ink displays have a wealth of possibilities for hardware innovation we haven’t seen yet.</p>
  623. <p>And you never know; you might someday find an obscure media query is perfect for your project, like <code>color-index</code> or <code>scan</code>.</p>
  624. <h2 id="the-deepest-backwards-compatibility-possible">The deepest backwards-compatibility possible</h2>
  625. <p>Start with a boring ol’ HTML 2.0 <code>&lt;img&gt;</code>:</p>
  626. <pre>
  627. <code class="html">&lt;img src="giraffe.jpg" alt="A giraffe."
  628. height="400" width="300"&gt;
  629. </code>
  630. </pre>
  631. <p>The key realization is that once we add <code>sizes</code> to this <code>&lt;img&gt;</code>, browsers that understand <code>&lt;picture&gt;</code> and width-based <code>srcset</code> will <em>ignore</em> the <code>src</code>, <code>height</code>, and <code>width</code> attributes.</p>
  632. <p><i>“Width-based <code>srcset</code>? What?”</i> Ah, my fine hypothetical friend, there are two kinds of <code>srcset</code> lists:</p>
  633. <dl>
  634. <dt>Width-based</dt>
  635. <dd><code>srcset="giraffe@1x.jpg 300w, giraffe@1.5x.jpg 450w, giraffe@2x.jpg 600w, giraffe@3x.jpg 900w"</code></dd>
  636. <dt>Pixel density-based</dt>
  637. <dd><code>srcset="giraffe@1.5x.jpg 1.5x, giraffe@2x.jpg 2x, giraffe@3x.jpg 3x"</code></dd>
  638. </dl>
  639. <p>The pixel density-based <code>srcset</code> (with the <code>x</code> descriptor) is the only kind that <a href="http://caniuse.com/#search=srcset">works in Safari, some earlier versions of Chrome &amp; Opera, and Microsoft Edge right now</a>. The newer width-based version (with the <code>w</code> descriptor) <a href="http://caniuse.com/#feat=picture">only works in the newest versions of Firefox, Opera, and Chrome</a>.</p>
  640. <p>We want to make our images as responsive as can be, so we should use both kinds of <code>srcset</code>, if possible. And we can! Just not quite how you would think. If we were to use a <code>srcset</code> with both <code>x</code> and <code>w</code> descriptors in it, like this:</p>
  641. <pre>
  642. <code>srcset="giraffe.jpg 100w, giraffe@2x.jpg 2x"
  643. </code>
  644. </pre>
  645. <p>Well, browsers will do <em>something,</em> but I guarantee it won’t be what you want. We’ll have to get multiple <code>srcset</code>s involved, using <code>&lt;picture&gt;</code> and <code>&lt;source&gt;</code>.</p>
  646. <p>First, for the almost-modern browsers, I'll bolt on the <code>x</code> descriptor <code>srcset</code>:</p>
  647. <pre>
  648. <code class="html">&lt;img src="giraffe.jpg" alt="A giraffe."
  649. srcset="giraffe@1.5x.jpg 1.5x,
  650. giraffe@2x.jpg 2x,
  651. giraffe@3x.jpg 3x"
  652. height="400" width="300"&gt;
  653. </code>
  654. </pre>
  655. <p>Unlike the <code>w</code> descriptor <code>srcset</code>, the <code>x</code> kind <em>does</em> take the <code>src</code> attribute into account. It assumes it’s <code>1x</code>, so you don’t have to specify it again. </p>
  656. <p>For <code>x</code> <code>srcset</code>, the <code>width</code> and <code>height</code> attributes just keep on doin’ what they’re doin’; all it does is swap out the image’s source if it’s on a higher-density screen. The new high-resolution source will take up the same amount of “CSS pixels” as before.</p>
  657. <p>Next, we’ll turbocharge the markup for browsers that understand <code>w</code> descriptor <code>srcset</code>:</p>
  658. <pre>
  659. <code class="html">&lt;picture&gt;
  660. &lt;source srcset="giraffe@1x.jpg 300w,
  661. giraffe@1.5x.jpg 450w,
  662. giraffe@2x.jpg 600w,
  663. giraffe@3x.jpg 900w"
  664. sizes="300px" type="image/jpeg"&gt;
  665. &lt;img src="giraffe.jpg" alt="A giraffe."
  666. srcset="giraffe@1.5x.jpg 1.5x,
  667. giraffe@2x.jpg 2x,
  668. giraffe@3x.jpg 3x"
  669. height="400" width="300"&gt;
  670. &lt;/picture&gt;
  671. </code>
  672. </pre>
  673. <p><i>“Hey, I thought you said that we should prefer plain <code>srcset</code>!”</i> Indeed, I did. If you look at the lone <code>&lt;source&gt;</code> element, we don’t have a <code>media</code> attribute, so browsers will use its <code>srcset</code> just like if it were on the <code>&lt;img&gt;</code> element instead. Same effect! (Note: the <code>type="image/jpeg"</code> isn't <em>necessary,</em> but the HTML won't validate without it.)</p>
  674. <p>This setup is as good as it gets, unless you want a responsive image polyfill for browsers that don’t understand <em>any</em> form of <code>srcset</code>. Me, I’m fine serving a sensible fallback <code>src</code> for my sites. Just don’t make it enormous!</p>
  675. <h2 id="height-and-width-constrained-srcset">Height <strong>and</strong> width-constrained <code>srcset</code></h2>
  676. <p>Say you’ve got a nice big, fullscreen image. It’s a content image, not style, so you’re using an actual <code>&lt;img&gt;</code> instead of a <code>background-image</code>. You want to responsify it, because fullscreen on a phone is orders of magnitude different from fullscreen on desktop.</p>
  677. <p>The first question is, is your image acting like <code>contain</code> or <code>cover</code>?</p>
  678. <p>If you know how CSS’s <code>background-size</code> or <code>object-fit</code> properties work, that’s what I’m asking. Is your image going to get as big as it can without cropping itself, or is it covering up an element entirely and not leave any empty space? If you’re confused, <a href="http://davidwalsh.name/demo/background-size.html">try playing with David Walsh’s demo</a>.</p>
  679. <h3 id="object-fit-cover">
  680. <code>object-fit: cover</code>
  681. </h3>
  682. <p>This one is easy: just set <code>sizes</code> to <code>100vw</code>.</p>
  683. <pre>
  684. <code class="html">&lt;img srcset="..." sizes="100vw" alt="A giraffe."&gt;
  685. </code>
  686. </pre>
  687. <h3 id="object-fit-contain">
  688. <code>object-fit: contain</code>
  689. </h3>
  690. <p>This one is hard.</p>
  691. <p>The problem is, <code>srcset</code> currently only does widths. <a href="https://github.com/ResponsiveImagesCG/picture-element/issues/86">There is an <code>h</code> descriptor coming</a>, but it doesn’t do us any good now. Are we doomed to make our images too tall, or to use <code>&lt;picture&gt;</code> and micromanage the browser logic?</p>
  692. <p>I shouted my despair into the ether, that is, <a href="http://stackoverflow.com/questions/29778235/can-i-use-picture-for-both-height-and-width-constrained-images">asked StackOverflow for ideas</a>. The inestimable <a href="https://github.com/aFarkas">Alexander Farkas</a>, maintainer of <a href="https://github.com/aFarkas/html5shiv">the HTML5shiv</a> since 2011, and the author of/contributor to loads of awesome <a href="https://github.com/scottjehl/picturefill">responsive</a> <a href="https://github.com/aFarkas/respimage">image</a> <a href="https://github.com/aFarkas/lazysizes">solutions</a>, swooped in and <a href="http://stackoverflow.com/a/29781000">nailed it in one</a>:</p>
  693. <blockquote>
  694. <p>I think I got it (1/2) is equal to (width/height):</p>
  695. <pre><code>&lt;img
  696. srcset="http://lorempixel.com/960/1920/sports/10/w1920/ 960w"
  697. sizes="(min-aspect-ratio: 1/2) calc(100vh * (1 / 2))"
  698. /&gt;</code></pre>
  699. </blockquote>
  700. <p>Pretty brilliant. I’ll explain this terse code example, because I sure as heck had to go over it several times myself.</p>
  701. <p>The lynchpin of the entire operation is the image’s <strong>aspect ratio.</strong> Without that, we can’t do a blamed thing.</p>
  702. <p>Thankfully, it’s straightforward: divide the image’s width by its height. This gets you the “aspect multiplier” (probably not a real term). The actual aspect <em>ratio</em> is that multiplier written as a simplified fraction. It’s commonly separated like 16:9, but in CSS it’s <code>16/9</code>.</p>
  703. <p>So if you had an image 300 pixels by 500 pixels, and another 600 pixels by 1000 pixels, they’d both have an aspect ratio of 3:5, or <code>3/5</code> in CSS.</p>
  704. <p>Let's pretend we have an image with aspect ratio 4:5. In English, our logic is:</p>
  705. <ol>
  706. <li>If the screen’s aspect ratio is wider (greater) than the image’s aspect ratio, the image will be the full height of the screen.</li>
  707. <li>Otherwise, the image will be the full width of the screen.</li>
  708. </ol>
  709. <p>Using our <code>4/5</code> aspect ratio image from earlier, that translates to:</p>
  710. <pre>
  711. <code class="html">&lt;img sizes="(min-aspect-ratio: 4/5) 80vh, 100vw"&gt;
  712. </code>
  713. </pre>
  714. <p>Whoah! Where did that <code>80vh</code> come from?</p>
  715. <p>This is the part I had trouble with. The first thing to understand is that we’re still telling the browser how <em>wide</em> the image is going to be. <strong>We</strong> know it’s going to be <code>100vh</code> tall, but we need to inform the browser what width that entails. Since we have the aspect ratio, and we have the height, we just need to solve for the width.</p>
  716. <dl class="proof">
  717. <dt class="math">Ratio = width ÷ height</dt>
  718. <dd>An image’s aspect ratio is its width divided by its height</dd>
  719. <dt class="math">⅘ = width ÷ 100vh</dt>
  720. <dd>Fill in our known values</dd>
  721. <dt class="math">width = ⅘ × 100vh</dt>
  722. <dd>Isolate the <var>width</var> variable</dd>
  723. <dt class="math">width = 80vh</dt>
  724. <dd>Simplify.</dd>
  725. </dl>
  726. <p>
  727. <small>(I wish more than just Firefox &amp; Safari implemented MathML.)</small>
  728. </p>
  729. <p>The second <code>sizes</code> argument, the lone <code>100vw</code> length, is for when the viewport is tall enough that the image is width-constrained, which means it will take up the entire screen width. It’s actually optional, since browsers will assume <code>100vw</code> by default if no media conditions are true. So we can slim down to <code>sizes="(min-aspect-ratio: 4/5) 80vh"</code>.</p>
  730. <p>That’s the hard part over with. <code>srcset</code> is just a list of images and widths, after all. Since this image should fill the screen of everyone, no matter the size, we’ll run the gamut between itty-bitty 300 pixels and big honking 4000 pixels. (Though what with smartwatches and those new 5k screens, that may not be enough…)</p>
  731. <pre>
  732. <code class="html">&lt;img sizes="(min-aspect-ratio: 4/5) 80vh"
  733. src="giraffe.png"
  734. srcset="giraffe@0.75x.jpg 300w,
  735. giraffe.png 400w,
  736. giraffe@1.5x.jpg 600w,
  737. giraffe@2x.jpg 800w,
  738. giraffe@2.5x.jpg 1000w,
  739. giraffe@3x.jpg 1200w,
  740. giraffe@3.5x.jpg 1400w,
  741. giraffe@4x.jpg 1600w,
  742. [...pretend I wrote out everything in between]
  743. giraffe@9.5x.jpg 3800w,
  744. giraffe@10x.jpg 4000w"
  745. alt="A giraffe."&gt;
  746. </code>
  747. </pre>
  748. <p>Is that an excessive amount of <code>srcset</code> choices? Yeah, probably. There’s no silver bullet to figure out what widths to include, so you’ll have to experiment with what works for your site. It <strong>is</strong> a lot of markup, but the HTML weight gain is more than paid back with properly-sized images.</p>
  749. <h2 id="responsive-bitmaps-inside-inline-svg">Responsive bitmaps inside inline SVG</h2>
  750. <p>My personal use-case is shoving raster images inside <code>&lt;svg&gt;</code>. You are probably not doing that, but the remainder of this blog post is as good an example as it gets for “how ugly can responsive images really be?”</p>
  751. <p>Answer: <strong>THIS UGLY.</strong> Children and the squeamish are allowed to cover their faces during the upsetting parts.</p>
  752. <p>Anyway, say I embed the raster image like this:</p>
  753. <pre>
  754. <code class="html">&lt;image xlink:href="giraffe.png" x="5" y="13" width="200" height="400"&gt;
  755. &lt;title&gt;A giraffe.&lt;/title&gt;
  756. &lt;/image&gt;
  757. </code>
  758. </pre>
  759. <p>This has two problems:</p>
  760. <ol>
  761. <li>SVG doesn’t have anything like <code>srcset</code>. You get the one <code>xlink:href</code>, <strong>and you’ll like it.</strong></li>
  762. <li>Browser preloaders don’t fetch it like they do with your usual <code>&lt;img src&gt;</code>. This is not a trivial slowdown; other assets (scripts, background images, etc.) can get scheduled before it, blocking the image.</li>
  763. </ol>
  764. <p>We can solve both issues in one fell swoop with <code>&lt;foreignObject&gt;</code>:</p>
  765. <pre>
  766. <code class="html">&lt;foreignObject x="5" y="13" width="200" height="400"&gt;
  767. &lt;img src="giraffe.png" srcset="image@2x.png 2x, etc..." alt="A giraffe."&gt;
  768. &lt;/foreignObject&gt;
  769. </code>
  770. </pre>
  771. <p>It can get as fancy as we want in there; <code>&lt;foreignObject&gt;</code> accepts any HTML that can go inside <code>&lt;body&gt;</code>. <strong><em>Except in Internet Explorer.</em></strong> (Edge, thankfully, does support <code>&lt;foreignObject&gt;</code>.)</p>
  772. <h3 id="fixing-ie">Fixing IE</h3>
  773. <p>IE9, 10, &amp; 11 all don’t support <code>&lt;foreignObject&gt;</code>, but hope is not lost: just use <code>&lt;switch&gt;</code> to give them <code>&lt;image</code> instead:</p>
  774. <pre>
  775. <code class="html">&lt;switch&gt;
  776. &lt;foreignObject x="5" y="13" width="200" height="400"
  777. requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;
  778. &lt;img src="giraffe.png" srcset="image@2x.png 400w, etc..." alt="a giraffe"&gt;
  779. &lt;/foreignObject&gt;
  780. &lt;image xlink:href="giraffe.png" x="5" y="13" width="200" height="400"&gt;
  781. &lt;title&gt;a giraffe&lt;/title&gt;
  782. &lt;/image&gt;
  783. &lt;/switch&gt;
  784. </code>
  785. </pre>
  786. <p>That <code>requiredFeatures</code> attribute lets IE know that the <code>&lt;foreignObject&gt;</code> element requires the features described at that URL. IE doesn’t like, look it up or anything, the URLs of what a browser supports are hardcoded in.</p>
  787. <p>A neat side-effect is that IE’s preloader will grab what’s inside the <code>src</code> it won’t use, but it’s the same URL as the <code>xlink:href</code> it <em>will</em> use. Bonus!</p>
  788. <h3 id="building-the-sizes">Building the <code>sizes</code></h3>
  789. <p>I’ve got a remaining wrinkle to deal with. The images don’t take up all of their <code>&lt;svg&gt;</code> parent’s space, and they each have their own aspect ratios! </p>
  790. <p>The first step to making sense of the mess is finding out what percentage of the <code>&lt;svg&gt;</code>’s dimensions each of the images’ dimensions take up. For a setup like this:</p>
  791. <pre>
  792. <code class="html">&lt;svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid"&gt;
  793. &lt;image x="200" y="200" width="1000" height="1200"/&gt;
  794. &lt;/svg&gt;
  795. </code>
  796. </pre>
  797. <p>(On the <code>&lt;svg&gt;</code> element, I’ve placed <code>preserveAspectRatio="xMidYMid"</code>, which has the viewBox act like <code>object-fit: contain</code> and center itself. )</p>
  798. <p>The image is roughly 71.43% the width of the <code>&lt;svg&gt;</code> (1000 ÷ 1400), and 75% the height (1200 ÷ 1600). We could write a basic program to do that math for us. But assembling that information into a usable <code>sizes</code> eluded me. Here were my conditions:</p>
  799. <ol class="proof">
  800. <li>If the screen’s aspect ratio is wider (greater) than the <code>&lt;svg&gt;</code>’s aspect ratio (7:8), the <code>&lt;svg&gt;</code> is height-constrained. Otherwise, it’s width-constrained.</li>
  801. <li><p>When the <code>&lt;svg&gt;</code> is width-constrained:</p>
  802. <dl>
  803. <dt class="math">Image’s display width = 100vw × 71.43%</dt>
  804. <dt class="math">Image’s display width = 71.43vw</dt>
  805. <dd>The <code>&lt;svg&gt;</code> will be 100vw wide, and the image will be 71.43% the width of that.</dd>
  806. </dl>
  807. </li><li><p>When the <code>&lt;svg&gt;</code> is height-constrained:</p>
  808. <dl>
  809. <dt class="math">Image’s display height = 100vh × 75%</dt>
  810. <dt class="math">Image’s display height = 75vh</dt>
  811. <dd>The <code>&lt;svg&gt;</code> will be 100vh tall, and the image will be 75% the height of that. However, we need its <em>width</em> for <code>sizes</code>.</dd>
  812. <dt class="math">Ratio = width ÷ height</dt>
  813. <dd>We’ll use the definition of aspect ratio again…</dd>
  814. <dt class="math">⅚ = width ÷ 75vh</dt>
  815. <dd>Substitute the values we know…</dd>
  816. <dt class="math">62.5vh = width</dt>
  817. <dd>And solve.</dd>
  818. </dl>
  819. </li>
  820. </ol>
  821. <p>Phew. After crunching the numbers, our <code>sizes</code> will look like this:</p>
  822. <pre>
  823. <code>sizes="(min-aspect-ratio: 7/8) 71.43vw, 62.5vh"
  824. </code>
  825. </pre>
  826. <p>Which, despite the contortions I took to make it, isn’t too scary-looking. Putting everything together, we end up with:</p>
  827. <pre>
  828. <code class="html">&lt;svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid"&gt;
  829. &lt;switch&gt;
  830. &lt;foreignObject x="200" y="200" width="1000" height="1000" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;
  831. &lt;img src="giraffe.png" srcset="image@2x.png 400w, etc..." alt="A giraffe." sizes="(min-aspect-ratio: 7/8) 71.43vw, 62.5vh"&gt;
  832. &lt;/foreignObject&gt;
  833. &lt;image xlink:href="giraffe.png" x="200" y="200" width="1000" height="1200"&gt;
  834. &lt;title&gt;A giraffe.&lt;/title&gt;
  835. &lt;/image&gt;
  836. &lt;/switch&gt;
  837. &lt;/svg&gt;
  838. </code>
  839. </pre>
  840. <p>Complicated, yes. But so is the Web.</p>
  841. <h3 id="making-things-worst-with-not-quite-entire-viewport-scaling">Making things worst with not-quite-entire-viewport scaling</h3>
  842. <p>As a final problem, I’m going to have interface chrome for navigating these SVGs, so I won’t have the full viewport available. I’ll need a fancier <code>sizes</code>.</p>
  843. <p>First, I need to know how much space the chrome will use. It’s set up to behave like this:</p>
  844. <ol>
  845. <li>If the screen is wider than it is tall (<code>orientation: landscape</code>), there’s chrome on all four sides. The horizontal chrome takes up 200 pixels, and the vertical chrome takes up 100 pixels.</li>
  846. <li>Otherwise, the chrome only takes up vertical space. It will be 200 pixels tall.</li>
  847. </ol>
  848. <p>So now I’ve got <strong>four</strong> boxes to analyze:</p>
  849. <ol>
  850. <li>The browser viewport</li>
  851. <li>The available space within that viewport once the chrome is done, taken up by the <code>&lt;svg&gt;</code> element</li>
  852. <li>The SVG viewport (“viewBox”)</li>
  853. <li>The <code>&lt;image&gt;</code> as constrained by the viewBox.</li>
  854. </ol>
  855. <p>(If any parts of this process have horrible browser bugs, <em>I’m going to be the first to find out.</em>)</p>
  856. <h4 id="the-browser-viewport">The browser viewport</h4>
  857. <p>Simple enough:</p>
  858. <ul>
  859. <li>width = 100vw</li>
  860. <li>height = 100vh</li>
  861. <li>aspect-ratio = 100vw/100vh</li>
  862. </ul>
  863. <h4 id="the-ltsvg-element">The <code>&lt;svg&gt;</code> element</h4>
  864. <ol class="proof">
  865. <li>
  866. <p>If the viewport is wider than it is tall (<code>orientation: landscape</code>):</p>
  867. <dl>
  868. <dt class="math">width = <code>calc(100vw - 200px)</code></dt>
  869. <dd>The chrome will take up 200px of width, so the width available is the full width (100vw) minus that (200px).</dd>
  870. <dt class="math">height = <code>calc(100vh - 100px)</code></dt>
  871. <dd>The chrome will take up 100px of height, and we calculate what’s left like we did with the width.</dd>
  872. <dt class="math">aspect ratio = <code>calc((100vw - 200px) / (100vh - 100px))</code></dt>
  873. <dd>Substitute the values into the equation for calculating aspect ratios.</dd>
  874. </dl>
  875. <p><small>(I’m using <code>calc()</code> because the units don’t match, and only the browser will know how to calculate them together.)</small></p>
  876. </li>
  877. <li>
  878. <p>Otherwise:</p>
  879. <dl>
  880. <dt class="math">width = 100vw</dt>
  881. <dd>There’s no horizontal chrome at all when the browser is <code>orientation: portrait</code>, so it’s just 100vw.</dd>
  882. <dt class="math">height = <code>calc(100vh - 200px)</code></dt>
  883. <dd>There is 200px of vertical chrome, though.</dd>
  884. <dt class="math">aspect ratio = <code>calc(100vw / (100vh - 200px))</code></dt>
  885. <dd>And substitute again.</dd>
  886. </dl>
  887. </li>
  888. </ol>
  889. <h4 id="the-viewbox">The viewBox</h4>
  890. <p>We’ll use the <code>&lt;svg&gt;</code> from last time:</p>
  891. <pre>
  892. <code class="html">&lt;svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid"&gt;
  893. ...
  894. &lt;/svg&gt;
  895. </code>
  896. </pre>
  897. <p>Unlike the browser viewport and the <code>&lt;svg&gt;</code> element, the aspect ratio of the viewBox doesn’t change when the orientation does. It’s also defined right inside the <code>viewBox</code> attribute. We could technically use <code>1400/1600</code>, but I’ll simplify it to <code>7/8</code>.</p>
  898. <p>As for the height and width, we have two media conditions that we need to check:</p>
  899. <ol>
  900. <li>Is the browser viewport wide or tall?`</li>
  901. <li>Does the available space have a wider or narrower aspect ratio than the viewBox?</li>
  902. </ol>
  903. <p>The first one is still <code>(orientation: landscape)</code>, but the chrome has complicated the second. Ideally, I could do <em>this</em>:</p>
  904. <pre>
  905. <code>(min-aspect-ratio: calc((100vw - 200px) / (100vh - 100px)))
  906. </code>
  907. </pre>
  908. <p>…but CSS requires <code>X/Y</code> string values for its <code>aspect-ratio</code> queries. Using <code>calc()</code> is right out.</p>
  909. <p>Instead, we have to <strong>reverse-engineer the media query.</strong> Querying the aspect ratio, after all, is just syntactic sugar for crunching the viewport width and height. Like so:</p>
  910. <p class="math">viewport’s aspect-ratio = viewport width ÷ viewport height = 100vw ÷ 100vh</p>
  911. <p>Let’s think about what we want using actual math symbols, instead of CSS’s hecked-up <code>min-</code>/<code>max-</code> system:</p>
  912. <dl class="proof">
  913. <dt class="math">aspect-ratio ≥ 7/8</dt>
  914. <dd>We want to know if the viewport’s aspect ratio is greater than or equal to 7/8 (the viewBox’s aspect ratio).</dd>
  915. <dt class="math">100vw ÷ 100vh ≥ 7/8</dt>
  916. <dd>Since the viewport’s aspect ratio equals 100vw/100vh, we substitute that.</dd>
  917. <dt class="math">100vw ≥ 7/8 × 100vh</dt>
  918. <dd>Multiply both sides by 100vh.</dd>
  919. <dt class="math">width ≥ 7/8 × 100vh</dt>
  920. <dd>Since 100vw is the width of the viewport, substitute that back in.</dd>
  921. <dt><code>min-width: calc(7/8 * 100vh)</code></dt>
  922. <dd>Convert into CSS syntax.</dd>
  923. </dl>
  924. <p>Now, if that’s all we wanted, I could precalculate the work for everyone’s browsers and use <code>(min-width: 87.5vh)</code>. But I haven’t introduced the chrome’s space requirements yet.</p>
  925. <ol>
  926. <li>When <code>(orientation: landscape)</code>, the available width = 100vw - 200px and the available height = 100vh - 100px</li>
  927. <li>Otherwise, the available width = 100vw and the available height = 100vh - 200px</li>
  928. </ol>
  929. <p>So with those complicating factors, what aspect ratio do we really want?</p>
  930. <ol class="proof">
  931. <li>
  932. <p>If <code>(orientation: landscape)</code>:</p>
  933. <dl>
  934. <dt class="math">aspect-ratio of available space = (100vw - 200px) ÷ (100vh - 100px)</dt>
  935. <dd>We want to find out if this is larger than 7/8.</dd>
  936. <dt class="math">(100vw - 200px) ÷ (100vh - 100px) ≥ 7/8</dt>
  937. <dd>Like this. We need to isolate either the 100vw or the 100vh so we can turn it into the <var>width</var> or <var>height</var> viewport dimension.</dd>
  938. <dt class="math">100vw - 200px ≥ 7/8 × (100vh - 100px)</dt>
  939. <dd>Multiply both sides by (100vh - 100px).</dd>
  940. <dt class="math">100vw ≥ (7/8 × (100vh - 100px)) + 200px</dt>
  941. <dd>Add 200px to both sides.</dd>
  942. <dt class="math">width ≥ (7/8 × (100vh - 100px)) + 200px</dt>
  943. <dd>Subsitute <var>width</var> for 100vw.</dd>
  944. <dt><code>min-width: calc((7/8 * (100vh - 100px)) + 200px)</code></dt>
  945. <dd>Convert to CSS syntax. (Now <em>that</em> is an ugly media condition.)</dd>
  946. </dl>
  947. </li>
  948. <li>
  949. <p>Otherwise:</p>
  950. <dl>
  951. <dt class="math">aspect-ratio of available space = 100vw/(100vh - 200px)</dt>
  952. <dd>Second verse, same as the first. This time the chrome isn’t quite as complicated.</dd>
  953. <dt class="math">100vw ≥ 7/8 × (100vh - 200px)</dt>
  954. <dd>Multiply both sides by (100vh - 200px).</dd>
  955. <dt class="math">width ≥ 7/8 × (100vh - 200px)</dt>
  956. <dd>Substitute <var>width</var> for 100vw.</dd>
  957. <dt><code>min-width: calc(7/8 * (100vh - 200px))</code></dt>
  958. <dd>Convert to CSS syntax.</dd>
  959. </dl>
  960. </li>
  961. </ol>
  962. <p>Okay, cool. Now we have the second half of our media conditions. So we need the dimensions of the viewBox for each of these:</p>
  963. <ol>
  964. <li><code>"(orientation: landscape) and (min-width: calc((7/8 * (100vh - 100px)) + 200px))"</code></li>
  965. <li><code>"(orientation: landscape)"</code></li>
  966. <li><code>"(min-width: calc(7/8 * (100vh - 200px)))"</code></li>
  967. <li><code>""</code></li>
  968. </ol>
  969. <p>Alright, let’s take it from the top.</p>
  970. <ol class="proof">
  971. <li>
  972. <p>If <code>"(orientation: landscape) and (min-width: calc((7/8 * (100vh - 100px)) + 200px))"</code>, the viewBox is height-constrained. That means we’ll need the height to figure out the width.</p>
  973. <dl>
  974. <dt class="math">viewBox’s height = available height = <code>calc(100vh - 100px)</code></dt>
  975. <dd>The viewBox will take up the entire height the <code>&lt;svg&gt;</code> element does.</dd>
  976. <dt class="math">aspect-ratio = height ÷ width</dt>
  977. <dt class="math">7/8 = <code>calc(100vh - 100px)</code> ÷ width</dt>
  978. <dd>Since we set the aspect ratio of the viewBox to be 7/8, we can substitute that and the width value we just figured into the aspect ratio equation.</dd>
  979. <dt class="math">7/8 × width = <code>calc(100vh - 100px)</code></dt>
  980. <dd>Multiply both sides by <var>width</var>.</dd>
  981. <dt class="math">width = <code>calc(100vh - 100px)</code> × 8/7</dt>
  982. <dd>Divide both sides by 7/8.</dd>
  983. <dt>width = <code>calc((100vh - 100px) * 8/7)</code></dt>
  984. <dd>Convert to CSS syntax.</dd>
  985. </dl>
  986. </li>
  987. <li>
  988. <p>If not, and if <code>"(orientation: landscape)"</code>, the viewBox is width-constrained.</p>
  989. <p>That means the the viewBox will take up all of the width of the <code>&lt;svg&gt;</code> element. Therefore, the width of the viewBox is also <code>calc(100vw - 200px)</code>.</p>
  990. </li>
  991. <li>
  992. <p>If not, and if <code>"(min-width: calc(7/8 * (100vh - 200px)))"</code>, the viewBox is height-constrained.</p>
  993. <dl>
  994. <dt class="math">height = <code>calc(100vh - 200px)</code></dt>
  995. <dd>The height that the <code>&lt;svg&gt;</code> element is taking up.</dd>
  996. <dt class="math">aspect-ratio = height ÷ width</dt>
  997. <dt class="math">7/8 = <code>calc(100vh - 200px)</code> ÷ width</dt>
  998. <dd>Substitute like before.</dd>
  999. <dt class="math">width = <code>calc(100vh - 200px)</code> × 8/7</dt>
  1000. <dd>I isolated the width variable in one step this time. if you forget how to do it, look back at Step 1.</dd>
  1001. <dt>width = <code>calc((100vh - 200px) * 8/7)</code></dt>
  1002. <dd>Convert to CSS syntax.</dd>
  1003. </dl>
  1004. </li>
  1005. <li>
  1006. <p>Otherwise, it’s width-constrained. This time, there’s no horizontal chrome at all, so the width is just 100vw.</p>
  1007. </li>
  1008. </ol>
  1009. <p>We’re getting there!</p>
  1010. <h4 id="the-image">The image</h4>
  1011. <p>Like the viewBox, we’ll use the <code>&lt;image&gt;</code> from last time:</p>
  1012. <pre>
  1013. <code class="html">&lt;svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid"&gt;
  1014. &lt;image x="200" y="200" width="1000" height="1200"/&gt;
  1015. &lt;/svg&gt;
  1016. </code>
  1017. </pre>
  1018. <p>We’re in the home stretch. Once we get the width values for this final box, we can assemble the <code>sizes</code>.</p>
  1019. <p>Unlike before, I’m not going to need the step-by-step equation solving. (If you were feeling patronized, don’t be… those were for <strong>me</strong> to understand what the hell I was doing.) We can take the width values of the viewBox and then multiply those by the percentage of the viewBox the image takes up. Which is…</p>
  1020. <p class="math">image-width ÷ viewBox-width = 1000/1400 = 71.42%</p>
  1021. <p>Of course, if you remember from way back at the top of this post, we can’t use percentages, so we’ll go with 0.7142 instead. Staple those widths onto the back of the media conditions and voilà:</p>
  1022. <ol>
  1023. <li><code>(orientation: landscape) and (min-width: calc((7/8 * (100vh - 100px)) + 200px)) calc((100vh - 100px) * 8/7 * 0.7142)</code></li>
  1024. <li><code>(orientation: landscape) calc((100vw - 200px) * 0.7142)</code></li>
  1025. <li><code>(min-width: calc(7/8 * (100vh - 200px))) calc((100vh - 200px) * 8/7 * 0.7142)</code></li>
  1026. <li><code>71.42vw</code></li>
  1027. </ol>
  1028. <p>What have I done.</p>
  1029. <h4 id="assembling">Assembling</h4>
  1030. <p>Putting it all together for the final markup:</p>
  1031. <pre>
  1032. <code class="html">&lt;svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid"&gt;
  1033. &lt;switch&gt;
  1034. &lt;foreignObject x="200" y="200" width="1000" height="1200" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;
  1035. &lt;picture&gt;
  1036. &lt;source srcset="giraffe@0.75x.jpg 300w,
  1037. giraffe.png 400w,
  1038. giraffe@1.5x.jpg 600w,
  1039. giraffe@2x.jpg 800w,
  1040. giraffe@2.5x.jpg 1000w,
  1041. giraffe@3x.jpg 1200w,
  1042. giraffe@3.5x.jpg 1400w,
  1043. giraffe@4x.jpg 1600w,
  1044. giraffe@4.5x.jpg 1800w,
  1045. giraffe@5x.jpg 2000w,
  1046. giraffe@5.5x.jpg 2200w,
  1047. giraffe@6x.jpg 2400w,
  1048. giraffe@6.5x.jpg 2600w,
  1049. giraffe@7x.jpg 2800w,
  1050. giraffe@7.5x.jpg 3000w,
  1051. giraffe@8x.jpg 3200w,
  1052. giraffe@8.5x.jpg 3400w,
  1053. giraffe@9x.jpg 3600w,
  1054. giraffe@9.5x.jpg 3800w,
  1055. giraffe@10x.jpg 4000w"
  1056. type="image/jpeg"
  1057. sizes="(orientation: landscape) and
  1058. (min-width: calc((7/8 * (100vh - 100px)) + 200px))
  1059. calc((100vh - 100px) * 8/7 * 0.7142),
  1060. (orientation: landscape)
  1061. calc((100vw - 200px) * 0.7142),
  1062. (min-width: calc(7/8 * (100vh - 200px)))
  1063. calc((100vh - 200px) * 8/7 * 0.7142),
  1064. 71.42vw"&gt;
  1065. &lt;img src="giraffe.jpg" alt="A giraffe."
  1066. srcset="giraffe@1.5x.jpg 1.5x,
  1067. giraffe@2x.jpg 2x,
  1068. giraffe@2.5x.jpg 2.5x,
  1069. giraffe@3x.jpg 3x,
  1070. giraffe@3.5x.jpg 3.5x,
  1071. giraffe@4x.jpg 4x,
  1072. giraffe@4.5x.jpg 4.5x,
  1073. giraffe@5x.jpg 5x"
  1074. height="400" width="300"&gt;
  1075. &lt;/picture&gt;
  1076. &lt;/foreignObject&gt;
  1077. &lt;image xlink:href="giraffe.png" x="200" y="200" width="1000" height="1200"&gt;
  1078. &lt;title&gt;A giraffe.&lt;/title&gt;
  1079. &lt;/image&gt;
  1080. &lt;/switch&gt;
  1081. &lt;/svg&gt;
  1082. </code>
  1083. </pre>
  1084. <p>If it weren’t in the requirements to support photographic images, I could replace <em>all of that</em> with a single <code>&lt;image xlink:href="giraffe.svg"&gt;</code>. But I guess I built character this way.</p>
  1085. <h2 id="thats-all-folks">That’s all, folks</h2>
  1086. <p>I hope that you don’t have to do what I’m doing, partially because of everything you just saw, but mostly because it won’t make you any money. (<em>Trust me.</em>). But if you run into tricky responsive image situations, hopefully something from this post will guide you a little.</p>
  1087. <p>Thanks for sticking with me!</p>
  1088. <p>
  1089. <small>(Code for the Web, they said. It’s fun, they said.)</small>
  1090. </p>
  1091. </article>
  1092. </section>
  1093. <nav id="jumpto">
  1094. <p>
  1095. <a href="/david/blog/">Accueil du blog</a> |
  1096. <a href="http://codepen.io/Tigt/post/when-responsive-images-get-ugly">Source originale</a> |
  1097. <a href="/david/stream/2019/">Accueil du flux</a>
  1098. </p>
  1099. </nav>
  1100. <footer>
  1101. <div>
  1102. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  1103. <p>
  1104. Bonjour/Hi!
  1105. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  1106. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  1107. </p>
  1108. <p>
  1109. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  1110. </p>
  1111. <p>
  1112. Voici quelques articles choisis :
  1113. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  1114. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  1115. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  1116. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  1117. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  1118. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  1119. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  1120. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  1121. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  1122. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  1123. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  1124. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  1125. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  1126. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  1127. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  1128. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  1129. </p>
  1130. <p>
  1131. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  1132. </p>
  1133. <p>
  1134. Je ne traque pas ta navigation mais mon
  1135. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  1136. conserve des logs d’accès.
  1137. </p>
  1138. </div>
  1139. </footer>
  1140. <script type="text/javascript">
  1141. ;(_ => {
  1142. const jumper = document.getElementById('jumper')
  1143. jumper.addEventListener('click', e => {
  1144. e.preventDefault()
  1145. const anchor = e.target.getAttribute('href')
  1146. const targetEl = document.getElementById(anchor.substring(1))
  1147. targetEl.scrollIntoView({behavior: 'smooth'})
  1148. })
  1149. })()
  1150. </script>