<!doctype html><!-- This is a valid HTML5 document. --> | |||||
<!-- Screen readers, SEO, extensions and so on. --> | |||||
<html lang="en"> | |||||
<!-- Has to be within the first 1024 bytes, hence before the `title` element | |||||
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset --> | |||||
<meta charset="utf-8"> | |||||
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 --> | |||||
<!-- The viewport meta is quite crowded and we are responsible for that. | |||||
See: https://codepen.io/tigt/post/meta-viewport-for-2015 --> | |||||
<meta name="viewport" content="width=device-width,initial-scale=1"> | |||||
<!-- Required to make a valid HTML5 document. --> | |||||
<title>How web bloat impacts users with slow devices (archive) — David Larlet</title> | |||||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||||
<!-- That good ol' feed, subscribe :). --> | |||||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||||
<!-- Is that even respected? Retrospectively? What a shAItshow… | |||||
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ --> | |||||
<meta name="robots" content="noai, noimageai"> | |||||
<!-- Documented, feel free to shoot an email. --> | |||||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||||
<script> | |||||
function toggleTheme(themeName) { | |||||
document.documentElement.classList.toggle( | |||||
'forced-dark', | |||||
themeName === 'dark' | |||||
) | |||||
document.documentElement.classList.toggle( | |||||
'forced-light', | |||||
themeName === 'light' | |||||
) | |||||
} | |||||
const selectedTheme = localStorage.getItem('theme') | |||||
if (selectedTheme !== 'undefined') { | |||||
toggleTheme(selectedTheme) | |||||
} | |||||
</script> | |||||
<meta name="robots" content="noindex, nofollow"> | |||||
<meta content="origin-when-cross-origin" name="referrer"> | |||||
<!-- Canonical URL for SEO purposes --> | |||||
<link rel="canonical" href="https://danluu.com/slow-device/"> | |||||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||||
<article> | |||||
<header> | |||||
<h1>How web bloat impacts users with slow devices</h1> | |||||
</header> | |||||
<nav> | |||||
<p class="center"> | |||||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use> | |||||
</svg> Accueil</a> • | |||||
<a href="https://danluu.com/slow-device/" title="Lien vers le contenu original">Source originale</a> | |||||
<br> | |||||
Mis en cache le 2024-03-25 | |||||
</p> | |||||
</nav> | |||||
<hr> | |||||
<p>In 2017, <a href="https://danluu.com/web-bloat/">we looked at how web bloat affects users with slow connections</a>. Even in the U.S., <a href="https://twitter.com/danluu/status/1116565029791260672">many users didn't have broadband speeds</a>, making much of the web difficult to use. It's still the case that many users don't have broadband speeds, both inside and outside of the U.S. and that much of the modern web isn't usable for people with slow internet, but the exponential increase in bandwidth (Nielsen suggests <abbr title="Unfortunately, I don't know of a public source for low-end data, say 10%-ile or 1%-ile; let me know if you have numbers on this">this is 50% per year for high-end connections</abbr>) has outpaced web bloat for typical sites, making this less of a problem than it was in 2017, although it's still a serious problem for people with poor connections.</p> | |||||
<p>CPU performance for web apps hasn't scaled nearly as quickly as bandwidth so, while more of the web is becoming accessible to people with low-end connections, more of the web is becoming inaccessible to people with low-end devices even if they have high-end connections. For example, if I try browsing a "modern" Discourse-powered forum on a <code>Tecno Spark 8C</code>, it sometimes crashes the browser. Between crashes, on measuring the performance, the responsiveness is significantly worse than browsing a BBS with an <code>8 MHz 286</code> and a <code>1200 baud</code> modem. On my <code>1Gbps</code> home internet connection, the <code>2.6 MB</code> compressed payload size "necessary" to load message titles is relatively light. The over-the-wire payload size has "only" increased by <code>1000x</code>, which is dwarfed by the increase in internet speeds. But the opposite is true when it comes to CPU speeds — for web browsing and forum loading performance, the <code>8-core (2 1.6 GHz Cortex-A75 / 6 1.6 GHz Cortex-A55)</code> CPU can't handle Discourse. The CPU is something like <code>100000x</code> faster than our <code>286</code>. Perhaps a <code>1000000x</code> faster device would be sufficient.</p> | |||||
<p>For anyone not familiar with the <code>Tecno Spark 8C</code>, today, a new <code>Tecno Spark 8C</code>, a quick search indicates that one can be hand for <code>USD 50-60</code> in Nigeria and perhaps <code>USD 100-110</code> in India. As <abbr title="The estimates for Nigerian median income that I looked at seem good enough, but the Indian estimate I found was a bit iffier; if you have a good source for Indian income distribution, please pass it along.">a fraction of median household income, that's substantially more than a current generation iPhone in the U.S. today.</abbr></p> | |||||
<p>By worldwide standards, the <code>Tecno Spark 8C</code> isn't even close to being a low-end device, so we'll also look at performance on an <code>Itel P32</code>, which is a lower end device (though still far from the lowest-end device people are using today). Additionally, we'll look at performance with an <code>M3 Max Macbook (14-core)</code>, an <code>M1 Pro Macbook (8-core)</code>, and the <code>M3 Max</code> set to <code>10x</code> throttling in Chrome dev tools. In order to give these devices every advantage, we'll be on fairly high-speed internet (1Gbps, with a WiFi router that's benchmarked as having lower latency under load than most of its peers). We'll look at some blogging platforms and micro-blogging platforms (this blog, Substack, Medium, Ghost, Hugo, Tumblr, Mastodon, Twitter, Threads, Bluesky, Patreon), forum platforms (Discourse, Reddit, Quora, vBulletin, XenForo, phpBB, and myBB), and platforms commonly used by small businesses (Wix, Squarespace, Shopify, and WordPress again).</p> | |||||
<p>In the table below, every row represents a website and every non-label column is a metric. After the website name column, we have the compressed size transferred over the wire (<code>wire</code>) and the raw, uncompressed, size (<code>raw</code>). Then we have, for each device, Largest Contentful Paint* (<code>LCP*</code>) and CPU usage on the main thread (<code>CPU</code>). Google's docs explain <code>LCP</code> as</p> | |||||
<blockquote> | |||||
<p>Largest Contentful Paint (LCP) measures when a user perceives that the largest content of a page is visible. The metric value for LCP represents the time duration between the user initiating the page load and the page rendering its primary content</p> | |||||
</blockquote> | |||||
<p><code>LCP</code> is a common optimization target because it's presented as one of the primary metrics in Google PageSpeed Insights, a "Core Web Vital" metric. There's an asterisk next to <code>LCP</code> as used in this document because, <code>LCP</code> as measured by Chrome is about painting a large fraction of the screen, as opposed to the definition above, which is about content. As sites have optimized for <code>LCP</code>, it's not uncommon to have a large paint (update) that's completely useless to the user, with the actual content of the page appearing well after the <code>LCP</code>. In cases where that happens, I've used the timestamp when useful content appears, not the <code>LCP</code> as defined by when a large but useless update occurs. The full details of the tests and why these metrics were chosen are discussed in an appendix.</p> | |||||
<p>Although CPU time isn't a "Core Web Vital", it's presented here because it's a simple metric that's highly correlated with my and other users' perception of usability on slow devices. See appendix for more detailed discussion on this. One reason CPU time works as a metric is that, if a page has great numbers for all other metrics but uses a ton of CPU time, the page is not going to be usable on a slow device. If it takes 100% CPU for 30 seconds, the page will be completely unusable for 30 seconds, and if it takes 50% CPU for 60 seconds, the page will be barely usable for 60 seconds, etc. Another reason it works is that, relative to commonly used metrics, it's hard to cheat on CPU time and make optimizations that significantly move the number without impacting user experience.</p> | |||||
<p>The color scheme in the table below is that, for sizes, more green = smaller / fast and more red = larger / slower. Extreme values are in black.</p> | |||||
<p> | |||||
</p> | |||||
<table> | |||||
<tr> | |||||
<th rowspan="2">Site</th><th colspan="2">Size</th><th colspan="2">M3 Max</th><th colspan="2">M1 Pro</th><th colspan="2">M3/10</th><th colspan="2">Tecno S8C</th><th colspan="2">Itel P32</th></tr> | |||||
<tr> | |||||
<th>wire</th><th>raw</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th></tr> | |||||
<tr> | |||||
<td class="l">danluu.com</td><td><font>6kB</font></td><td><font>18kB</font></td><td><font>50ms</font></td><td><font>20ms</font></td><td><font>50ms</font></td><td><font>30ms</font></td><td><font>0.2s</font></td><td><font>0.3s</font></td><td>0.4s</td><td><font>0.3s</font></td><td>0.5s</td><td>0.5s</td></tr> | |||||
<tr> | |||||
<td class="l">HN</td><td><font>11kB</font></td><td><font>50kB</font></td><td><font>0.1s</font></td><td><font>30ms</font></td><td><font>0.1s</font></td><td><font>30ms</font></td><td><font>0.3s</font></td><td><font>0.3s</font></td><td>0.5s</td><td>0.5s</td><td>0.7s</td><td>0.6s</td></tr> | |||||
<tr> | |||||
<td class="l">MyBB</td><td><font>0.1MB</font></td><td><font>0.3MB</font></td><td><font>0.3s</font></td><td><font>0.1s</font></td><td><font>0.3s</font></td><td><font>0.1s</font></td><td>0.6s</td><td>0.6s</td><td>0.8s</td><td>0.8s</td><td>2.1s</td><td>1.9s</td></tr> | |||||
<tr> | |||||
<td class="l">phpBB</td><td>0.4MB</td><td>0.9MB</td><td><font>0.3s</font></td><td><font>0.1s</font></td><td>0.4s</td><td><font>0.1s</font></td><td>0.7s</td><td>1.1s</td><td>1.7s</td><td>1.5s</td><td>4.1s</td><td>3.9s</td></tr> | |||||
<tr> | |||||
<td class="l">WordPress</td><td>1.4MB</td><td>1.7MB</td><td><font>0.2s</font></td><td><font>60ms</font></td><td><font>0.2s</font></td><td><font>80ms</font></td><td>0.7s</td><td>0.7s</td><td>1s</td><td>1.5s</td><td>1.2s</td><td>2.5s</td></tr> | |||||
<tr> | |||||
<td class="l">WordPress (old)</td><td>0.3MB</td><td>1.0MB</td><td><font>80ms</font></td><td><font>70ms</font></td><td><font>90ms</font></td><td><font>90ms</font></td><td>0.4s</td><td>0.9s</td><td>0.7s</td><td>1.7s</td><td>1.1s</td><td>1.9s</td></tr> | |||||
<tr> | |||||
<td class="l">XenForo</td><td>0.3MB</td><td>1.0MB</td><td>0.4s</td><td><font>0.1s</font></td><td>0.6s</td><td><font>0.2s</font></td><td>1.4s</td><td>1.5s</td><td>1.5s</td><td>1.8s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Ghost</td><td>0.7MB</td><td>2.4MB</td><td><font>0.1s</font></td><td><font>0.2s</font></td><td><font>0.2s</font></td><td><font>0.2s</font></td><td>1.1s</td><td>2.2s</td><td>1s</td><td>2.4s</td><td>1.1s</td><td>3.5s</td></tr> | |||||
<tr> | |||||
<td class="l">vBulletin</td><td>1.2MB</td><td>3.4MB</td><td>0.5s</td><td><font>0.2s</font></td><td>0.6s</td><td><font>0.3s</font></td><td>1.1s</td><td>2.9s</td><td>4.4s</td><td>4.8s</td><td>13s</td><td>16s</td></tr> | |||||
<tr> | |||||
<td class="l">Squarespace</td><td>1.9MB</td><td>7.1MB</td><td><font>0.1s</font></td><td>0.4s</td><td><font>0.2s</font></td><td>0.4s</td><td>0.7s</td><td>3.6s</td><td>14s</td><td>5.1s</td><td>16s</td><td>19s</td></tr> | |||||
<tr> | |||||
<td class="l">Mastodon</td><td>3.8MB</td><td>5.3MB</td><td><font>0.2s</font></td><td><font>0.3s</font></td><td><font>0.2s</font></td><td>0.4s</td><td>1.8s</td><td>4.7s</td><td>2.0s</td><td>7.6s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Tumblr</td><td>3.5MB</td><td>7.1MB</td><td>0.7s</td><td>0.6s</td><td>1.1s</td><td>0.7s</td><td>1.0s</td><td>7.0s</td><td>14s</td><td>7.9s</td><td>8.7s</td><td>8.7s</td></tr> | |||||
<tr> | |||||
<td class="l">Quora</td><td>0.6MB</td><td>4.9MB</td><td>0.7s</td><td>1.2s</td><td>0.8s</td><td>1.3s</td><td>2.6s</td><td>8.7s</td><td><font>FAIL</font></td><td><font>FAIL</font></td><td>19s</td><td><font>29s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Bluesky</td><td>4.8MB</td><td>10MB</td><td>1.0s</td><td>0.4s</td><td>1.0s</td><td>0.5s</td><td>5.1s</td><td>6.0s</td><td>8.1s</td><td>8.3s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Wix</td><td>7.0MB</td><td>21MB</td><td>2.4s</td><td>1.1s</td><td>2.5s</td><td>1.2s</td><td>18s</td><td>11s</td><td>5.6s</td><td>10s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Substack</td><td>1.3MB</td><td>4.3MB</td><td>0.4s</td><td>0.5s</td><td>0.4s</td><td>0.5s</td><td>1.5s</td><td>4.9s</td><td>14s</td><td>14s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Threads</td><td>9.3MB</td><td>13MB</td><td>1.5s</td><td>0.5s</td><td>1.6s</td><td>0.7s</td><td>5.1s</td><td>6.1s</td><td>6.4s</td><td>16s</td><td><font>28s</font></td><td><font>66s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Twitter</td><td>4.7MB</td><td>11MB</td><td>2.6s</td><td>0.9s</td><td>2.7s</td><td>1.1s</td><td>5.6s</td><td>6.6s</td><td>12s</td><td>19s</td><td>24s</td><td><font>43s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Shopify</td><td>3.0MB</td><td>5.5MB</td><td>0.4s</td><td><font>0.2s</font></td><td>0.4s</td><td><font>0.3s</font></td><td>0.7s</td><td>2.3s</td><td>10s</td><td><font>26s</font></td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Discourse</td><td>2.6MB</td><td>10MB</td><td>1.1s</td><td>0.5s</td><td>1.5s</td><td>0.6s</td><td>6.5s</td><td>5.9s</td><td>15s</td><td><font>26s</font></td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Patreon</td><td>4.0MB</td><td>13MB</td><td>0.6s</td><td>1.0s</td><td>1.2s</td><td>1.2s</td><td>1.2s</td><td>14s</td><td>1.7s</td><td><font>31s</font></td><td>9.1s</td><td><font>45s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Medium</td><td>1.2MB</td><td>3.3MB</td><td>1.4s</td><td>0.7s</td><td>1.4s</td><td>1s</td><td>2s</td><td>11s</td><td>2.8s</td><td><font>33s</font></td><td>3.2s</td><td><font>63s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Reddit</td><td>1.7MB</td><td>5.4MB</td><td>0.9s</td><td>0.7s</td><td>0.9s</td><td>0.9s</td><td>6.2s</td><td>12s</td><td>1.2s</td><td><font>∞</font></td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
</table> | |||||
<p>At a first glance, the table seems about right, in that the sites that feel slow unless you have a super fast device show up as slow in the table (as in, <code>max(LCP*,CPU))</code> is high on lower-end devices). When I polled folks about what platforms they thought would be fastest and slowest on our slow devices (<a href="https://mastodon.social/@danluu/111994437263038931">Mastodon</a>, <a href="https://twitter.com/danluu/status/1761875263359537652">Twitter</a>, <a href="https://www.threads.net/@danluu.danluu/post/C3yVpfKS-RP">Threads</a>), they generally correctly predicted that Wordpress and Ghost and Wordpress would be faster than Substack and Medium, and that Discourse would be much slower than old PHP forums like phpBB, XenForo, and vBulletin. I also pulled Google PageSpeed Insights (PSI) scores for pages (not shown) and the correlation isn't as strong with those numbers <abbr title="For the 'real world' numbers, this is also because users with slow devices can't really use some of these sites, so their devices aren't counted in the distribution and PSI doesn't normalize for this.">because</abbr> a handful of sites have managed to optimize their PSI scores without actually speeding up their pages for users.</p> | |||||
<p>If you've never used a low-end device like this, the general experience is that many sites are unusable on the device and loading anything resource intensive (an app or a huge website) can cause crashes. Doing something too intense in a resource intensive app can also cause crashes. While <a href="https://www.youtube.com/watch?v=U1JMRFQWK70">reviews note</a> that <a href="https://www.youtube.com/watch?v=McawfNlydqk">you can run PUBG and other 3D games with decent performance</a> on a <code>Tecno Spark 8C</code>, this doesn't mean that the device is fast enough to read posts on modern text-centric social media platforms or modern text-centric web forums. While <code>40fps</code> is achievable in PUBG, we can easily see less than <code>0.4fps</code> when scrolling on these sites.</p> | |||||
<p>We can see from the table how many of the sites are unusable if you have a slow device. All of the pages with <code>10s+ CPU</code> are a fairly bad experience even after the page loads. Scrolling is very jerky, frequently dropping to a few frames per second and sometimes well below. When we tap on any link, the delay is so long that we can't be sure if our tap actually worked. If we tap again, we can get the dreaded situation where the first tap registers, which then causes the second tap to do the wrong thing, but if we wait, we often end up waiting too long because the original tap didn't actually register (or it registered, but not where we thought it did). Although MyBB doesn't serve up a mobile site and is penalized by Google for not having a mobile friendly page, it's actually much more usable on these slow mobiles than all but the fastest sites because scrolling and tapping actually work.</p> | |||||
<p>Another thing we can see is how much variance there is in the relative performance on different devices. For example, comparing an <code>M3/10</code> and a <code>Tecno Spark 8C</code>, for danluu.com and Ghost, an <code>M3/10</code> gives a halfway decent approximation of the <code>Tecno Spark 8C</code> (although danluu.com loads much too quickly), but the <code>Tecno Spark 8C</code> is about three times slower (<code>CPU</code>) for Medium, Substack, and Twitter, roughly four times slower for Reddit and Discourse, and over an order of magnitude faster for Shopify. For Wix, the <code>CPU</code> approximation is about accurate, but our `<code>Tecno Spark 8C</code> is more than 3 times slower on <code>LCP*</code>. It's great that Chrome lets you conveniently simulate a slower device from the convenience of your computer, but just enabling Chrome's CPU throttling (or using any combination of out-of-the-box options that are available) gives fairly different results than we get on many real devices. The full reasons for this are beyond the scope of the post; for the purposes of this post, it's sufficient to note that slow pages are often super-linearly slow as devices get slower and that slowness on one page doesn't strongly predict slowness on another page.</p> | |||||
<p>If take a site-centric view instead of a device-centric view, another way to look at it is that sites like Discourse, Medium, and Reddit, don't use all that much CPU on our fast <code>M3</code> and <code>M1</code> computers, but they're among the slowest on our <code>Tecno Spark 8C</code> (Reddit's CPU is shown as <code>∞</code> because, no matter how long we wait with no interaction, Reddit uses <code>~90% CPU</code>). Discourse also sometimes crashed the browser after interacting a bit or just waiting a while. For example, one time, the browser crashed after loading Discourse, scrolling twice, and then leaving the device still for a minute or two. For consistency's sake, this wasn't marked as <code>FAIL</code> in the table since the page did load but, realistically, having a page so resource intensive that the browser crashes is a significantly worse user experience than any of the <code>FAIL</code> cases in the table. When we looked at how <a href="https://danluu.com/web-bloat/">web bloat impacts users with slow connections</a>, we found that <abbr title="One thing to keep in mind here is that having a slow device and a slow connection have multiplicative impacts.">much of the web was unusable for people with slow connections and slow devices are no different</abbr>.</p> | |||||
<p>Another pattern we can see is how the older sites are, in general, faster than the newer ones, with sites that (visually) look like they haven't been updated in a decade or two tending to be among the fastest. For example, MyBB, the least modernized and oldest looking forum is <code>3.6x / 5x faster (LCP* / CPU)</code> than Discourse on the <code>M3</code>, but on the <code>Tecno Spark 8C</code>, the difference is <code>19x / 33x</code> and, given the overall scaling, it seems safe to guess that the difference would be even larger on the Itel P32 if Discourse worked on such a cheap device.</p> | |||||
<p>Another example is Wordpress (old) vs. newer, trendier, blogging platforms like Medium and Substack. Wordpress (old) is is <code>17.5x / 10x faster (LCP* / CPU)</code> than Medium and <code>5x / 7x faster (LCP* / CPU)</code> faster than Substack on our <code>M3 Max</code>, and <code>4x / 19x</code> and <code>20x / 8x</code> faster, respectively, on our <code>Tecno Spark 8C</code>. Ghost is a notable exception to this, being a modern platform (launched a year after Medium) that's competitive with older platforms (modern Wordpress is also arguably an exception, but many folks would probably still consider that to be an old platform). Among forums, NodeBB also seems to be a bit of an exception (see appendix for details).</p> | |||||
<p>Sites that use modern techniques like partially loading the page and then dynamically loading the rest of it, such as Discourse, Reddit, and Substack, tend to be less usable than the scores in the table indicate. Although, in principle, you could build such a site in a simple way that works well with cheap devices but, in practice sites that use dynamic loading tend to be complex enough that the sites are extremely janky on low-end devices. It's generally difficult or impossible to scroll a predictable distance, which means that users will sometimes accidentally trigger more loading by scrolling too far, causing the page to lock up. Many pages actually remove the parts of the page you scrolled past as you scroll; all such pages are essentially unusable. Other basic web features, like page search, also generally stop working. Pages with this kind of dynamic loading can't rely on the simple and fast ctrl/command+F search and have to build their own search. How well this works varies (this used to work quite well in Google docs, but for the past few months or maybe a year, it takes so long to load that I have to deliberately wait after opening a doc to avoid triggering the browser's useless built in search; Discourse search has never really worked on slow devices or even not very fast but not particular slow devices).</p> | |||||
<p>In principle, these modern pages that burn a ton of CPU when loading could be doing pre-work that means that later interactions on the page are faster and cheaper than on the pages that do less up-front work (this is a common argument in favor of these kinds of pages), but that's not the case for pages tested, which are slower to load initially, slower on subsequent loads, and slower after they've loaded.</p> | |||||
<p>To understand why the theoretical idea that doing all this work up-front doesn't generally result in a faster experience later, this exchange between a distinguished engineer at Google and one of the founders of Discourse (and CEO at the time) is <abbr title="the founder has made similar comments elsewhere as well, so this isn't a one-off analogy for him, nor do I find it to be an unusual line of thinking in general">illustrative</abbr>, in <a href="https://danluu.com/jeff-atwood-trashes-qualcomm-engineering.png">a discussion where the founder of Discourse says that you should test mobile sites on laptops with throttled bandwidth but not throttled CPU</a>:</p> | |||||
<ul> | |||||
<li><b>Google</b>: *you* also don't have slow 3G. These two settings go together. Empathy needs to extend beyond iPhone XS users in a tunnel.</li> | |||||
<li><b>Discourse</b>: Literally any phone of vintage iPhone 6 or greater is basically as fast as the "average" laptop. You have to understand how brutally bad Qualcomm is at their job. Look it up if you don't believe me.</li> | |||||
<li><b>Google</b>: I don't need to believe you. I know. This is well known by people who care. | |||||
My point was that just like not everyone has a fast connection not everyone has a fast phone. Certainly the iPhone 6 is frequently very CPU bound on real world websites. But that isn't the point.</li> | |||||
<li><b>Discourse</b>: we've been trending towards infinite CPU speed for decades now (and we've been asymptotically there for ~5 years on desktop), what we are not and will never trend towards is infinite bandwidth. Optimize for the things that matter. and I have zero empathy for @qualcomm. Fuck Qualcomm, they're terrible at their jobs. I hope they go out of business and the ground their company existed on is plowed with salt so nothing can ever grow there again.</li> | |||||
<li><b>Google</b>: Mobile devices are not at all bandwidth constraint in most circumstances. They are latency constraint. Even the latest iPhone is CPU constraint before it is bandwidth constraint. If you do well on 4x slow down on a MBP things are pretty alright</li> | |||||
<li>...</li> | |||||
<li><b>Google</b>: Are 100% of users on iOS?</li> | |||||
<li><b>Discourse</b>: The influential users who spend money tend to be, I’ll tell you that ... Pointless to worry about cpu, it is effectively infinite already on iOS, and even with Qualcomm’s incompetence, will be within 4 more years on their embarrassing SoCs as well</li> | |||||
</ul> | |||||
<p>When someone asks the founder of Discourse, "just wondering why you hate them", he responds with a link that cites the Kraken and Octane benchmarks from <a href="https://www.anandtech.com/show/9146/the-samsung-galaxy-s6-and-s6-edge-review/5">this Anandtech review</a>, which have the Qualcomm chip at 74% and 85% of the performance of the then-current Apple chip, respectively.</p> | |||||
<p>The founder and then-CEO of Discourse considers Qualcomm's mobile performance embarrassing and finds this so offensive that he thinks Qualcomm engineers should all lose their jobs for delivering <abbr title="I think it could be reasonable to cite a lower number, but I'm using the number he cited, not what I would cite">74% to 85% of the performance of Apple</abbr>. Apple has what I consider to be an all-time great performance team. Reasonable people could disagree on that, but one has to at least think of them as a world-class team. So, producing a product with <abbr title="recall that, on a Tecno Spark 8, Discourse is 33 times slower than MyBB, which isn't particularly optimized for performance">74% to 85% of an all-time-great team is considered an embarrassment worthy of losing your job</abbr>.</p> | |||||
<p>There are two attitudes on display here which I see in a lot of software folks. First, that CPU speed is infinite and one shouldn't worry about CPU optimization. And second, that gigantic speedups from hardware should be expected and the only reason hardware engineers wouldn't achieve them is due to spectacular incompetence, so the slow software should be blamed on hardware engineers, not software engineers. Donald Knuth expressed a similar sentiment in</p> | |||||
<blockquote> | |||||
<p>I might as well flame a bit about my personal unhappiness with the current trend toward multicore architecture. To me, it looks more or less like the hardware designers have run out of ideas, and that they’re trying to pass the blame for the future demise of Moore’s Law to the software writers by giving us machines that work faster only on a few key benchmarks! I won’t be surprised at all if the whole multiithreading idea turns out to be a flop, worse than the "Itanium" approach that was supposed to be so terrific—until it turned out that the wished-for compilers were basically impossible to write. Let me put it this way: During the past 50 years, I’ve written well over a thousand programs, many of which have substantial size. I can’t think of even five of those programs that would have been enhanced noticeably by parallelism or multithreading. Surely, for example, multiple processors are no help to TeX ... I know that important applications for parallelism exist—rendering graphics, breaking codes, scanning images, simulating physical and biological processes, etc. But all these applications require dedicated code and special-purpose techniques, which will need to be changed substantially every few years. Even if I knew enough about such methods to write about them in TAOCP, my time would be largely wasted, because soon there would be little reason for anybody to read those parts ... The machine I use today has dual processors. I get to use them both only when I’m running two independent jobs at the same time; that’s nice, but it happens only a few minutes every week.</p> | |||||
</blockquote> | |||||
<p>In the case of Discourse, a hardware engineer is an embarrassment not deserving of a job if they can't hit 90% of the performance of an all-time-great performance team but, as a software engineer, delivering 3% the performance of a non-highly-optimized application like MyBB is no problem. In Knuth's case, <abbr title="using this term loosely, to include materials scientists, etc., which is consistent with Knuth's comments">hardware engineers</abbr> gave programmers a 100x performance increase every decade for decades with little to no work on the part of programmers. The moment this slowed down and programmers had to adapt to take advantage of new hardware, hardware engineers were "all out of ideas", but learning a few "new" (1970s and 1980s era) ideas to take advantage of current hardware would be a waste of time. And <a href="https://www.patreon.com/posts/54329188">we've previously discussed Alan Kay's claim that hardware engineers are "unsophisticated" and "uneducated" and aren't doing "real engineering" and how we'd get a 1000x speedup if we listened to Alan Kay's "sophisticated" ideas</a>.</p> | |||||
<p>It's fairly common for programmers to expect that hardware will solve all their problems, and then, when that doesn't happen, pass the issue onto the user, explaining why the programmer needn't do anything to help the user. A question one might ask is how much performance improvement programmers have given us. There are cases of algorithmic improvements that result in massive speedups but, as we noted above, Discourse, the fastest growing forum software today, seems to have given us an approximately <code>1000000x</code> slowdown in performance.</p> | |||||
<p>Another common attitude on display above is the idea that users who aren't wealthy don't matter. When asked if 100% of users are on iOS, the founder of Discourse says "The influential users who spend money tend to be, I’ll tell you that". We see the same attitude all over comments on <a href="https://tonsky.me/blog/js-bloat/">Tonsky's JavaScript Bloat post</a>, with people expressing <a href="https://danluu.com/cocktail-ideas/">cocktail-party sentiments</a> like "Phone apps are hundreds of megs, why are we obsessing over web apps that are a few megs? Starving children in Africa can download Android apps but not web apps? Come on" and "surely no user of gitlab would be poor enough to have a slow device, let's be serious" (paraphrased for length).</p> | |||||
<p>But when we look at the size of apps that are downloaded in Africa, we see that people who aren't on high-end devices use apps like Facebook Lite (a couple megs) and commonly use apps that are a single digit to low double digit number of megabytes. There are multiple reasons app makers care about their app size. One is just the total storage available on the phone; if you watch real users install apps, they often have to delete and uninstall things to put a new app on, so the smaller size is both easier to to install and has a lower chance of being uninstalled when the user is looking for more space. Another is that, if you look at data on app size and usage (I don't know of any public data on this; please pass it along if you have something public I can reference), when large apps increase the size and memory usage, they get more crashes, which drives down user retention, growth, and engagement and, conversely, when they optimize their size and memory usage, they get fewer crashes and better user retention, growth, and engagement.</p> | |||||
<p><a href="https://infrequently.org/2024/01/performance-inequality-gap-2024/">Alex Russell points out that iOS has 7% market share in India (a 1.4B person market) and 6% market share in Latin America (a 600M person market)</a>. Although the founder of Discourse says that these aren't "influential users" who matter, these are still real human beings. Alex further points out that, according to Windows telemetry, which covers the vast majority of desktop users, most laptop/desktop users are on low-end machines which are likely slower than a modern iPhone.</p> | |||||
<p>On the bit about no programmers having slow devices, I know plenty of people who are using hand-me-down devices that are old and slow. Many of them aren't even really poor; they just don't see why (for example) their kid needs a super fast device, and they don't understand how much of the modern web works poorly on slow devices. After all, the "slow" device can play 3d games and (with the right OS) compile codebases like Linux or Chromium, so why shouldn't the device be able to interact with a site like gitlab?</p> | |||||
<p>Contrary to the claim from the founder of Discourse that, within years, every Android user will be on some kind of super fast Android device, it's been six years since his comment and it's going to be at least a decade before almost everyone in the world who's using a phone has a high-speed device and this could easily take two decades or more. If you look up marketshare stats for Discourse, it's extremely successful; it appears to be the fastest growing forum software in the world by a large margin. The impact of having the fastest growing forum software in the world created by an organization whose then-leader was willing to state that he doesn't really care about users who aren't "influential users who spend money", who don't have access to "infinite CPU speed", is that a lot of forums are now inaccessible to people who don't have enough wealth to buy a device with effectively infinite CPU.</p> | |||||
<p>If the founder of Discourse were an anomaly, this wouldn't be too much of a problem, but he's just verbalizing the implicit assumptions a lot of programmers have, which is why we see that so many modern websites are unusable if you buy the income-adjusted equivalent of a new, current generation, iPhone in a low-income country.</p> | |||||
<p><i>Thanks to Yossi Kreinen, Fabian Giesen, John O'Nolan, Joseph Scott, Loren McIntyre, Daniel Filan, @acidshill, Alex Russell, Chris Adams, Tobias Marschner, Matt Stuchlik, @gekitsu@toot.cat, Justin Blank, Andy Kelley, Julian Lam, Matthew Thomas, avarcat, @eamon@social.coop, and David Turner for comments/corrections/discussion.</i></p> | |||||
<h3 id="appendix-gaming-lcp">Appendix: gaming LCP</h3> | |||||
<p>We noted above that we used <code>LCP*</code> and not <code>LCP</code>. This is because <code>LCP</code> basically measures when the largest change happens. When this metric was not deliberately gamed in ways that don't benefit the user, this was a great metric, but this metric has become less representative of the actual user experience as more people have gamed it. In the less blatant cases, people do small optimizations that improve <code>LCP</code> but barely improve or don't improve the actual user experience.</p> | |||||
<p>In the more blatant cases, developers will deliberately flash a very large change on the page as soon as possible, generally a loading screen that has no value to the user (actually negative value because doing this increases the total amount of work done and the total time it takes to load the page) and then they carefully avoid making any change large enough that any later change would get marked as the <code>LCP</code>.</p> | |||||
<p>For the same reason <a href="https://en.wikipedia.org/wiki/Volkswagen_emissions_scandal">that VW didn't publicly discuss how it was gaming its emissions numbers</a>, developers tend to shy away from discussing this kind of <code>LCP</code> optimization in public. An exception to this is Discourse, where <a href="https://meta.discourse.org/t/introducing-discourse-splash-a-visual-preloader-displayed-while-site-assets-load/232003" rel="nofollow">they publicly announced this kind of <code>LCP</code> optimization, with comments from their devs and the then-CTO (now CEO)</a>, noting that their new "Discourse Splash" feature hugely reduced <code>LCP</code> for sites after they deployed it. And then developers ask why their <code>LCP</code> is high, the standard advice from Discourse developers is to keep elements smaller than the "Discourse Splash", so that the <code>LCP</code> timestamp is computed from this useless element that's thrown up to optimize <code>LCP</code>, as opposed to having the timestamp be computed from any actual element that's relevant to the user. <a href="https://meta.discourse.org/t/theme-components-and-largest-contentful-paint-lcp/258680" rel="nofollow">Here's a typical, official, comment from Discourse</a></p> | |||||
<blockquote> | |||||
<p>If your banner is larger than the element we use for the "Introducing Discourse Splash - A visual preloader displayed while site assets load" you gonna have a bad time for LCP.</p> | |||||
</blockquote> | |||||
<p>The official response from Discourse is that you should make sure that your content doesn't trigger the <code>LCP</code> measurement and that, instead, our loading animation timestamp is what's used to compute `LCP.</p> | |||||
<p>The sites with the most extreme ratio of <code>LCP</code> of useful content vs. Chrome's measured <code>LCP</code> were:</p> | |||||
<ul> | |||||
<li>Wix | |||||
<ul> | |||||
<li><code>M3</code>: <code>6</code></li> | |||||
<li><code>M1</code>: <code>12</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>3</code></li> | |||||
<li><code>Itel P32</code>: <code>N/A</code> <code>(FAIL)</code></li> | |||||
</ul></li> | |||||
<li>Discourse: | |||||
<ul> | |||||
<li><code>M3</code>: <code>10</code></li> | |||||
<li><code>M1</code>: <code>12</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>4</code></li> | |||||
<li><code>Itel P32</code>: <code>N/A</code> <code>(FAIL)</code></li> | |||||
</ul></li> | |||||
</ul> | |||||
<p>Although we haven't discussed the gaming of other metrics, it appears that some websites also game other metrics and "optimize" them even when this has no benefit to users.</p> | |||||
<h3 id="appendix-the-selfish-argument-for-optimizing-sites">Appendix: the selfish argument for optimizing sites</h3> | |||||
<p>This will depend on the scale of the site as well as its performance, but when I've looked at this data for large companies I've worked for, improving site and app performance is worth a mind boggling amount of money. It's measurable in A/B tests and it's also among the interventions that has, in <abbr title="where you keep a fraction of users on the old arm of the A/B test for a long duration, sometimes a year or more, in order to see the long-term impact of a change">long-term holdbacks</abbr>, a relatively large impact on growth and retention (many interventions test well but don't look as good long term, whereas performance improvements tend to look better long term).</p> | |||||
<p>Of course you can see this from the direct numbers, but you can also implicitly see this in a lot of ways when looking at the data. One angle is that (just for example), at Twitter, user-observed p99 latency was about <code>60s</code> in India as well as a number of African countries (even excluding relatively wealthy ones like Egypt and South Africa) and also about <code>60s</code> in the United States. Of course, across the entire population, people have faster devices and connections in the United States, but in every country, there are enough users that have slow devices or connections that the limiting factor is really user patience and not the underlying population-level distribution of devices and connections. Even if you don't care about users in Nigeria or India and only care about U.S. ad revenue, improving performance for low-end devices and connections has enough of impact that we could easily see the impact in global as well as U.S. revenue in A/B tests, especially in long-term holdbacks. And you also see the impact among users who have fast devices since a change that improves the latency for a user with a "low-end" device from <code>60s</code> to <code>50s</code> might improve the latency for a user with a high-end device from <code>5s</code> to <code>4.5s</code>, which has an impact on revenue, growth, and retention numbers as well.</p> | |||||
<p>For <a href="https://danluu.com/bad-decisions/">a variety of reasons that are beyond the scope of this doc</a>, this kind of boring, quantifiable, growth and revenue driving work has been difficult to get funded at most large companies I've worked for relative to flash product work that ends up showing little to no impact in long-term holdbacks.</p> | |||||
<h3 id="appendix-designing-for-low-performance-devices">Appendix: designing for low performance devices</h3> | |||||
<p>When using slow devices or any device with low bandwidth and/or poor connectivity, the best experiences, by far, are generally the ones that load a lot of content at once into a static page. If the images have proper width and height attributes and alt text, that's very helpful. Progressive images (as in progressive jpeg) isn't particularly helpful.</p> | |||||
<p>On a slow device with high bandwidth, any lightweight, static, page works well, and lightweight dynamic pages can work well if designed for performance. Heavy, dynamic, pages are doomed unless the page weight doesn't cause the page to be complex.</p> | |||||
<p>With low bandwidth and/or poor connectivity, lightweight pages are fine. With heavy pages, the best experience I've had is when I trigger a page load, go do something else, and then come back when it's done (or at least the HTML and CSS are done). I can then open each link I might want to read in a new tab, and then do something else while I wait for those to load.</p> | |||||
<p>A lot of the optimizations that modern websites do, such as partial loading that causes more loading when you scroll down the page, and the concomitant hijacking of search (because the browser's built in search is useless if the page isn't fully loaded) causes the interaction model that works to stop working and makes pages very painful to interact with.</p> | |||||
<p>Just for example, a number of people have noted that Substack performs poorly for them because it does partial page loads. <a href="https://danluu.com/substack.mp4">Here's a video by @acidshill of what it looks like to load a Substack article and then scroll on an iPhone 8</a>, where the post has a fairly fast <code>LCP</code>, but if you want to scroll past the header, you have to wait <code>6s</code> for the next page to load, and then on scrolling again, you have to wait maybe another <code>1s</code> to <code>2s</code>:</p> | |||||
<p>As an example of the opposite approach, I tried loading some fairly large plain HTML pages, such as <a href="https://danluu.com/diseconomies-scale/">https://danluu.com/diseconomies-scale/</a> (<code>0.1 MB wire</code> / <code>0.4 MB raw</code>) and <a href="https://danluu.com/threads-faq/">https://danluu.com/threads-faq/</a> (<code>0.4 MB wire</code> / <code>1.1 MB raw</code>) and these were still quite usable for me even on slow devices. <code>1.1 MB</code> seems to be larger than optimal and breaking that into a few different pages would be better on a low-end devices, but a single page with <code>1.1 MB</code> of text works much better than most modern sites on a slow device. While you can get into trouble with HTML pages that are so large that browsers can't really handle them, for pages with a normal amount of content, it generally isn't until you have <a href="https://nolanlawson.com/2023/01/17/my-talk-on-css-runtime-performance/">complex CSS payloads</a> or JS that the pages start causing problems for slow devices. Below, we test pages that are relatively simple, some of which have a fair amount of media (<code>14 MB</code> in one case) and find that these pages work ok, as long as they stay simple.</p> | |||||
<p>Chris Adams has also noted that blind users, using screen readers, often report that dynamic loading makes the experience much worse for them. Like dynamic loading to improve performance, while this can be done well, it's often either done badly or bundled with so much other complexity that the result is worse than a simple page.</p> | |||||
<p>@Qingcharles noted another accessibility issue — the (prison) parolees he works with are given "lifeline" phones, which are often very low end devices. From a quick search, in 2024, some people will get an iPhone 6 or an iPhone 8, but there are also plenty of devices that are lower end than an Itel P32, let alone a Tecno Spark 8C. They also get plans with highly limited data, and then when they run out, some people "can't fill out any forms for jobs, welfare, or navigate anywhere with Maps".</p> | |||||
<p>For sites that do up-front work and actually give you a decent experience on low end devices, Andy Kelley pointed out an example of a site that does up front work that seems to work ok on a slow device (although it would struggle on a very slow connection), <a href="https://ziglang.org/documentation/master/std/">the Zig standard library documentation</a>:</p> | |||||
<blockquote> | |||||
<p>I made the controversial decision to have it fetch all the source code up front and then do all the content rendering locally. In theory, this is CPU intensive but in practice... even those old phones have really fast CPUs!</p> | |||||
</blockquote> | |||||
<p>On the <code>Tecno Spark 8C</code>, this uses <code>4.7s</code> of CPU and, afterwards, is fairly responsive (relative to the device — <a href="https://danluu.com/input-lag/">of course an iPhone responds much more quickly</a>. Taps cause links to load fairly quickly and scrolling also works fine (it's a little jerky, but almost nothing is really smooth on this device). This seems like the kind of thing people are referring to when they say that you can get better performance if you ship a heavy payload, but there aren't many examples of that which actually improve performance on low-end devices.</p> | |||||
<h3 id="appendix-articles-on-web-performance-issues">Appendix: articles on web performance issues</h3> | |||||
<ul> | |||||
<li>2015: Maciej Cegłowski: <a href="https://idlewords.com/talks/website_obesity.htm">The Website Obesity Crisis</a> | |||||
<ul> | |||||
<li>Size: <code>1.0 MB</code> / <code>1.1 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.9s</code> / <code>1.4s</code> | |||||
<ul> | |||||
<li>Scrolling a bit jerky, images take a little bit of time to appear if scrolling very quickly (jumping halfway down page from top), but delay is below what almost any user would perceive when scrolling a normal distance.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2015: Nate Berkopec: <a href="https://www.speedshop.co/2015/11/05/page-weight-doesnt-matter.html">Page Weight Doesn't Matter</a> | |||||
<ul> | |||||
<li>Size: <code>80 kB</code> / <code>0.2 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.8s</code> / <code>0.7s</code> | |||||
<ul> | |||||
<li>Does lazy loading, page downloads <code>650 kB</code> / <code>1.8 MB</code> if you scroll through the entire page, but scrolling is only a little jerky and the lazy loading doesn't cause delays. Probably the only page I've tried that does lazy loading in a way that makes the experience better and not worse on a slow device; I didn't test on a slow connection, where this would still make the experience worse.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>1.1s</code> / <code>1s</code> | |||||
<ul> | |||||
<li>Scrolling basically unusable; scroll extremely jerky and moves a random distance, often takes over <code>1s</code> for text to render when scrolling to new text; can be much worse with images that are lazy loaded. Even though this is the best implementation of lazy loading I've seen in the wild, the <code>Itel P32</code> still can't handle it.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2017: Dan Luu: <a href="https://danluu.com/web-bloat/">How web bloat impacts users with slow connections</a> | |||||
<ul> | |||||
<li>Size: <code>14 kB</code> / <code>57 kB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.5s</code> / <code>0.3s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>:<code>0.7s</code> / <code>0.5 s</code></li> | |||||
</ul></li> | |||||
<li>2017-2024+: Alex Russell: <a href="https://infrequently.org/series/performance-inequality/">The Performance Inequality Gap (series)</a> | |||||
<ul> | |||||
<li>Size: <code>82 kB</code> / <code>0.1 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.5s</code> / <code>0.4s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>0.7s</code> / <code>0.4s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2024: Nikita Prokopov (Tonsky): <a href="https://tonsky.me/blog/js-bloat/">JavaScript Bloat in 2024</a> | |||||
<ul> | |||||
<li>Size: <code>14 MB</code> / <code>14 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.8s</code> / <code>1.9s</code> | |||||
<ul> | |||||
<li>When scrolling, it takes a while for images to show up (500ms or so) and the scrolling isn't smooth, but it's not jerky enough that it's difficult to scroll to the right place.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>2.5s</code> / <code>3s</code> | |||||
<ul> | |||||
<li>Scrolling isn't smooth. Scrolling accurately is a bit difficult, but can generally scroll to where you want if very careful. Generally takes a bit more than <code>1s</code> for new content to appear when you scroll a significant distance.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2024: Dan Luu: <a href="https://danluu.com/slow-device/">This post</a> | |||||
<ul> | |||||
<li>Size: <code>25 kB</code> / <code>74 kB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.6s</code> / <code>0.5s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>1.3s</code> / <code>1.1s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine, although I had to make a change for this to be the case — this doc originally had an embedded video, which the <code>Itel P32</code> couldn't really handle. | |||||
<ul> | |||||
<li>Note that, while these numbers are worse than the numbers for "Page Weight Doesn't Matter", this page is usable after load, which that other page isn't beacuse it execute some kind of lazy loading that's too complex for this phone to handle in a reasonable timeframe. | |||||
<br></li> | |||||
</ul></li> | |||||
</ul></li> | |||||
</ul></li> | |||||
</ul> | |||||
<h3 id="appendix-empathy-for-non-rich-users">Appendix: empathy for non-rich users</h3> | |||||
<p>Something I've observed over time, as programming has become more prestigious and more lucrative, is <a href="https://mastodon.social/@danluu/109901711437753852">that people have tended to come from wealthier backgrounds</a> and have less exposure to people with different income levels. An example we've discussed before, is at a well-known, prestigious, startup that has a very left-leaning employee base, where everyone got rich, on a discussion about the covid stimulus checks, in a slack discussion, a well meaning progressive employee said that it was pointless because people would just use their stimulus checks to buy stock. This person had, apparently, never talked to any middle-class (let alone poor) person about where their money goes or looked at the data on who owns equity. And that's just looking at American wealth. When we look at world-wide wealth, the general level of understanding is much lower. People seem to really underestimate the dynamic range in wealth and income across the world. From having talked to quite a few people about this, a lot of people seem to have mental buckets for "poor by American standards" (buys stock with stimulus checks) and "poor by worldwide standards" (maybe doesn't even buy stock), but the range of poverty in the world dwarfs the range of poverty in America to an extent that not many wealthy programmers seem to realize.</p> | |||||
<p>Just for example, <a href="https://mastodon.social/@danluu/109537302116865694">in this discussion how lucky I was (in terms of financial opportunities) that my parents made it to America</a>, someone mentioned that it's not that big a deal because they had great financial opportunities in Poland. For one thing, with respect to the topic of the discussion, the probability that someone will end up with a high-paying programming job (senior staff eng at a high-paying tech company) or equivalent, I suspect that, when I was born, being born poor in the U.S. gives you better odds than being fairly well off in Poland, but I could believe the other case as well if presented with data. But if we're comparing Poland v. U.S. to Vietnam v. U.S., if I spend <abbr title="so, these probably aren't the optimal numbers one would use for a comparison, but I think they're good enough for this purpose">15 seconds looking up rough wealth numbers for these countries</abbr> in the year I was born, the GDP/capita ratio of U.S. : Poland was ~8:1, whereas it was ~50 : 1 for Poland : Vietnam. The difference in wealth between Poland and Vietnam was roughly the square of the difference between the U.S. and Poland, so Poland to Vietnam is roughly equivalent to Poland vs. some hypothetical country that's richer than the U.S. by the amount that the U.S. is richer than Poland. These aren't even remotely comparable, but a lot of people seem to have this mental model that there's "rich countries" and "not rich countries" and "not rich countries" are all roughly in the same bucket. GDP/capita isn't ideal, but it's easier to find than percentile income statistics; the quick search I did also turned up that annual income in Vietnam then was something like $200-$300 a year. Vietnam was also going through the tail end of a famine whose impacts are a bit difficult to determine because statistics here seem to be gamed, but if you believe the mortality rate statistics, the famine caused total overall mortality rate to jump to double the normal baseline.</p> | |||||
<p>Of course, at the time, the median person in a low-income country wouldn't have had a computer, let alone internet access. But, today it's fairly common for people in low-income countries to have devices. Many people either don't seem to realize this or don't understand what sorts of devices a lot of these folks use.</p> | |||||
<p>On the Discourse founder's comments on iOS vs. Android marketshare, Fabian notes</p> | |||||
<blockquote> | |||||
<p>In the US, according to the most recent data I could find (for 2023), iPhones have around 60% marketshare. In the EU, it's around 33%. | |||||
This has knock-on effects. Not only do iOS users skew towards the wealthier end, they also skew towards the US.</p> | |||||
<p>There's some secondary effects from this too. For example, in the US, iMessage is very popular for group chats etc. and infamous for interoperating very poorly with Android devices in a way that makes the experience for Android users very annoying (almost certainly intentionally so).</p> | |||||
<p>In the EU, not least because Android is so much more prominent, iMessage is way less popular and anecdotally, even iPhone users among my acquaintances who would probably use iMessage in the US tend to use WhatsApp instead.</p> | |||||
<p>Point being, globally speaking, recent iOS + fast Internet is even more skewed towards a particular demographic than many app devs in the US seem to be aware.</p> | |||||
</blockquote> | |||||
<p>And on the comment about mobile app vs. web app sizes, Fabian said:</p> | |||||
<blockquote> | |||||
<p>One more note from experience: apps you install when you install them, and generally have some opportunity to hold off on updates while you're on a slow or metered connection (or just don't have data at all).</p> | |||||
<p>Back when I originally got my US phone, I had no US credit history and thus had to use prepaid plans. I still do because it's fine for what I actually use my phone for most of the time, but it does mean that when I travel to Germany once a year, I don't get data roaming at all. (Also, phone calls in Germany cost me $1.50 apiece, even though T-Mobile is the biggest mobile provider in Germany - though, of course, not T-Mobile US.)</p> | |||||
<p>Point being, I do get access to free and fast Wi-Fi at T-Mobile hotspots (e.g. major train stations, airports etc.) and on inter-city trains that have them, but I effectively don't have any data plan when in Germany at all.</p> | |||||
<p>This is completely fine with mobile phone apps that work offline and sync their data when they have a connection. But web apps are unusable while I'm not near a public Wi-Fi.</p> | |||||
<p>Likewise I'm fine sending an email over a slow metered connection via the Gmail app, but I for sure wouldn't use any web-mail client that needs to download a few MBs worth of zipped JS to do anything on a metered connection.</p> | |||||
<p>At least with native app downloads, I can prepare in advance and download them while I'm somewhere with good internet!</p> | |||||
</blockquote> | |||||
<p>Another comment from Fabian (this time paraphrased since this was from a conversation), is that people will often justify being quantitatively hugely slower because there's a qualitative reason something should be slow. One example he gave was that screens often take a long time to sync their connection and this is justified because there are operations that have to be done that take time. For a long time, these operations would often take seconds. Recently, a lot of displays sync much more quickly because Nvidia specifies how long this can take for something to be "G-Sync" certified, so display makers actually do this in a reasonable amount of time now. While it's true that there are operations that have to be done that take time, there's no fundamental reason they should take as much time as they often used to. Another example he gave was on how someone was justifying how long it took to read thousands of files because the operation required a lot of syscalls and "syscalls are slow", which is a qualitatively true statement, but if you look at the actual cost of a syscall, in the case under discussion, the cost of a syscall was many orders of magnitude from being costly enough to be a reasonable explanation for why it took so long to read thousands of files.</p> | |||||
<p>On this topic, when people point out that a modern website is slow, someone will generally respond with the qualitative defense that the modern website has these great features, which the older website is lacking. And while it's true that (for example) Discourse has features that MyBB doesn't, it's hard to argue that its feature set justifies being <code>33x</code> slower.</p> | |||||
<h3 id="appendix-experimental-details">Appendix: experimental details</h3> | |||||
<p>With the exception of danluu.com and, arguably, HN, for each site, I tried to find the "most default" experience. For example, for WordPress, this meant a demo blog with the current default theme, twentytwentyfour. In some cases, this may not be the most likely thing someone uses today, e.g., for Shopify, I looked at the first thing that theme they give you when you browse their themes, but I didn't attempt to find theme data to see what the most commonly used theme is. For this post, I wanted to do all of the data collection and analysis as a short project, something that takes less than a day, so there were a number of shortcuts like this, which will be described below. I don't think it's wrong to use the first-presented Shopify theme in a decent fraction of users will probably use the first-presente theme, but that is, of course, less representative than grabbing whatever the most common theme is and then also testing many different sites that use that theme to see how real-world performance varies when people modify the theme for their own use. If I worked for Shopify or wanted to do competitive analysis on behalf of a competitor, I would do that, but for a one-day project on how large websites impact users on low-end devices, the performance of Shopify demonstrated here seems ok. I actually <a href="https://mastodon.social/@danluu/111994372051118539">did the initial work for this around when I ran these polls</a>, back in February; I just didn't have time to really write this stuff up for a month.</p> | |||||
<p>For the tests on laptops, I tried to have the laptop at ~60% battery, not plugged in, and the laptop was idle for enough time to return to thermal equilibrium in a room at 20°C, so pages shouldn't be impacted by prior page loads or other prior work that was happening on the machine.</p> | |||||
<p>For the mobile tests, the phones were at ~100% charge and plugged in, and also previously at 100% charge so the phones didn't have any heating effect you can get from rapidly charging. As noted above, these tests were formed with <code>1Gbps</code> WiFi. No other apps were running, the browser had no other tabs open, and the only apps that were installed on the device, so no additional background tasks should've been running other than whatever users are normally subject to by the device by default. A real user with the same device is going to see worse performance than we measured here in almost every circumstance except if running Chrome Dev Tools on a phone significantly degrades performance. I noticed that, on the Itel P32, scrolling was somewhat jerkier with Dev Tools running than when running normally but, since this was a one-day project, I didn't attempt to quantify this and if it impacts some sites much more than others. In absolute terms, the overhead can't be all that large because the fastest sites are still fairly fast with Dev Tools running, but if there's some kind of overhead that's super-linear in the amount of work the site does (possibly indirectly, if it causes some kind of resource exhaustion), then that could be a problem in measurements of some sites.</p> | |||||
<p>Sizes were all measured on mobile, so in cases where different assets are loaded on mobile vs. desktop, the we measured the mobile asset sizes. <code>CPU</code> was measured as CPU time on the main thread (I did also record time on other threads for sites that used other threads, but didn't use this number; if <code>CPU</code> were a metric people wanted to game, time on other threads would have to be accounted for to prevent sites from trying to offload as much work as possible to other threads, but this isn't currently an issue and time on main thread is more directly correlated to usability than sum of time across all threads, and the metric that would work for gaming is less legible with no upside for now).</p> | |||||
<p>For WiFi speeds, speed tests had the following numbers:</p> | |||||
<ul> | |||||
<li><code>M3 Max</code> | |||||
<ul> | |||||
<li>Netflix (fast.com) | |||||
<ul> | |||||
<li>Download: <code>850 Mbps</code></li> | |||||
<li>Upload: <code>840 Mbps</code></li> | |||||
<li>Latency (unloaded / loaded): <code>3ms</code> / <code>8ms</code></li> | |||||
</ul></li> | |||||
<li>Ookla | |||||
<ul> | |||||
<li>Download: <code>900 Mbps</code></li> | |||||
<li>Upload: <code>840 Mbps</code></li> | |||||
<li>Latency (unloaded / download / upload): <code>3ms</code> / <code>8ms</code> / <code>13ms</code></li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li><code>Tecno Spark 8C</code> | |||||
<ul> | |||||
<li>Netflix (fast.com) | |||||
<ul> | |||||
<li>Download: <code>390 Mbps</code></li> | |||||
<li>Upload: <code>210 Mbps</code></li> | |||||
<li>Latency (unloaded / loaded): <code>2ms</code> / <code>30ms</code></li> | |||||
</ul></li> | |||||
<li>Oookla | |||||
<ul> | |||||
<li>Ookla web app fails, can't see results</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code> | |||||
<ul> | |||||
<li>Netflix | |||||
<ul> | |||||
<li>Download: <code>44 Mbps</code></li> | |||||
<li>Upload: test fails to work (sends one chunk of data and then hangs, sending no more data)</li> | |||||
<li>Latency (unloaded / loaded): <code>4ms</code> / <code>400ms</code></li> | |||||
</ul></li> | |||||
<li>Okta | |||||
<ul> | |||||
<li>Download: <code>45 Mbps</code></li> | |||||
<li>Upload: test fails to work</li> | |||||
<li>Latency: test fails to display latency</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
</ul> | |||||
<p>One thing to note is that the <code>Itel P32</code> doesn't really have the ability to use the bandwidth that it nominally has. Looking at the top Google reviews, none of them mention this. <a href="https://www.nairaland.com/4628841/itel-p32-review-great-those" rel="nofollow">The first review reads</a></p> | |||||
<blockquote> | |||||
<p>Performance-wise, the phone doesn’t lag. It is powered by the latest Android 8.1 (GO Edition) ... we have 8GB+1GB ROM and RAM, to run on a power horse of 1.3GHz quad-core processor for easy multi-tasking ... I’m impressed with the features on the P32, especially because of the price. I would recommend it for those who are always on the move. And for those who take battery life in smartphones has their number one priority, then P32 is your best bet.</p> | |||||
</blockquote> | |||||
<p><a href="https://techjaja.com/itel-p32-review-dual-camera-smartphone-alarming-price-tag/" rel="nofollow">The second review reads</a></p> | |||||
<blockquote> | |||||
<p>Itel mobile is one of the leading Africa distributors ranking 3rd on a continental scale ... the light operating system acted up to our expectations with no sluggish performance on a 1GB RAM device ... fairly fast processing speeds ... the Itel P32 smartphone delivers the best performance beyond its capabilities ... at a whooping UGX 330,000 price tag, the Itel P32 is one of those amazing low-range like smartphones that deserve a mid-range flag for amazing features embedded in a single package.</p> | |||||
</blockquote> | |||||
<p><a href="https://pctechmag.com/2018/08/itel-p32-full-review-much-more-than-just-a-budget-entry-level-smartphone/" rel="nofollow">The third review reads</a></p> | |||||
<blockquote> | |||||
<p>"Much More Than Just a Budget Entry-Level Smartphone ... Our full review after 2 weeks of usage ... While switching between apps, and browsing through heavy web pages, the performance was optimal. There were few lags when multiple apps were running in the background, while playing games. However, the overall performance is average for maximum phone users, and is best for average users [screenshot of game] Even though the game was skipping some frames, and automatically dropped graphical details it was much faster if no other app was running on the phone.</p> | |||||
</blockquote> | |||||
<p>Notes on sites:</p> | |||||
<ul> | |||||
<li>Wix | |||||
<ul> | |||||
<li>www.wix.com/website-template/view/html/3173?originUrl=https%3A%2F%2Fwww.wix.com%2Fwebsite%2Ftemplates%2Fhtml%2Fmost-popular&tpClick=view_button&esi=a30e7086-28db-4e2e-ba22-9d1ecfbb1250: this was the first entry when I clicked to get a theme</li> | |||||
<li><code>LCP</code> was misleading on every device</li> | |||||
<li>On the <code>Tecno Spark 8C</code>, scrolling never really works. It's very jerky and this never settles down</li> | |||||
<li>On the <code>Itel P32</code>, the page fails non-deterministically (different errors on different loads); it can take quite a while to error out; it was <code>23s</code> on the first run, with the CPU pegged for <code>28s</code></li> | |||||
</ul></li> | |||||
<li>Patreon | |||||
<ul> | |||||
<li>www.patreon.com/danluu: used my profile where possible</li> | |||||
<li>Scrolling on Patreon and finding old posts is so painful that I maintain <a href="https://danluu.com/#pt">my own index of my Patreon posts</a> so that I can find my old posts without having to use Patreon. Although Patreon's numbers in the table don't look that bad in the table when you're on a fast laptop, that's just for the initial load. The performance as you scroll is bad enough that I don't think that, today, there exists a computer and internet connection that browse Patreon with decent performance.</li> | |||||
</ul></li> | |||||
<li>Threads | |||||
<ul> | |||||
<li>threads.net/danluu.danluu: used my profile where possible</li> | |||||
<li>On the <code>Itel P32</code>, this technically doesn't load correctly and could be marked as <code>FAIL</code>, but it's close enough that I counted it. The thing that's incorrect is that profile photos have a square box around then | |||||
<ul> | |||||
<li>However, as with the other heavy pages, interacting with the page doesn't really work and the page is unusable, but this appears to be for the standard performance reasons and not because the page failed to render</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>Twitter | |||||
<ul> | |||||
<li>twitter.com/danluu: used my profile where possible</li> | |||||
</ul></li> | |||||
<li>Discourse | |||||
<ul> | |||||
<li>meta.discourse.org: this is what turned up when I searched for an official forum.</li> | |||||
<li>As discussed above, the <code>LCP</code> is highly gamed and basically meaningless. We linked to a post where the Discourse folks note that, on slow loads, they put a giant splash screen up at <code>2s</code> to cap the <code>LCP</code> at <code>2s</code>. Also notable is that, on loads that are faster than the 2s, the <code>LCP</code> is also highly gamed. For example, on the <code>M3 Max</code> with low-latency <code>1Gbps</code> internet, the <code>LCP</code> was reported as <code>115ms</code>, but the page loads actual content at <code>1.1s</code>. This appears to use the same fundamental trick as "Discourse Splash", in that it paints a huge change onto the screen and then carefully loads smaller elements to avoid having the actual page content detected as the <code>LCP</code>.</li> | |||||
<li>On the <code>Tecno Spark 8C</code>, scrolling is unpredictable and can jump too far, triggering loading from infinite scroll, which hangs the page for <code>3s-10s</code>. Also, the entire browser sometimes crashes if you just let the browser sit on this page for a while.</li> | |||||
<li>On the <code>Itel P32</code>, an error message is displayed after <code>7.5s</code></li> | |||||
</ul></li> | |||||
<li>Bluesky | |||||
<ul> | |||||
<li>bsky.app/profile/danluu.com</li> | |||||
<li>Displays a blank screen on the <code>Itel P32</code></li> | |||||
</ul></li> | |||||
<li>Squarespace | |||||
<ul> | |||||
<li>cedar-fluid-demo.squarespace.com: this was the second theme that showed up when I clicked themes to get a theme; the first was one called "Bogart", but that was basically a "coming soon" single page screen with no content, so I used the second theme instead of the first one.</li> | |||||
<li>A lot of errors and warnings in the console with the <code>Itel P32</code>, but the page appears to load and work, although interacting with it is fairly slow and painful</li> | |||||
<li><code>LCP</code> on the <code>Tecno Spark 8C</code> was significantly before the page content actually loaded</li> | |||||
</ul></li> | |||||
<li>Tumblr | |||||
<ul> | |||||
<li>www.tumblr.com/slatestarscratchpad: used this because I know this tumblr exists. I don't read a lot of tumblers (maybe three or four), and this one seemed like the closest thing to my blog that I know of on tumblr.</li> | |||||
<li>This page fails on the <code>Itel P32</code>, but doesn't <code>FAIL</code>. The console shows that the JavaScript errors out, but the page still works fine (I tried scrolling, clicking links, etc., and these all worked), so you can actually go to the post you want and read it. The JS error appears to have made this page load much more quickly than it other would have and also made interacting with the page after it loaded fairly zippy.</li> | |||||
</ul></li> | |||||
<li>Shopify | |||||
<ul> | |||||
<li>themes.shopify.com/themes/motion/styles/classic/preview?surface_detail=listing&surface_inter_position=1&surface_intra_position=1&surface_type=all: this was the first theme that showed up when I looked for themes</li> | |||||
<li>On the first <code>M3/10</code> run, Chrome dev tools reported a nonsensical <code>697s</code> of CPU time (the run completed in a normal amount of time, well under <code>697s</code> or even <code>697/10s</code>. This run was ignored when computing results.</li> | |||||
<li>On the <code>Itel P32</code>, the page load never completes and it just shows a flashing cursor-like image, which is deliberately loaded by the theme. On devices that load properly, the flashing cursor image is immediately covered up by another image, but that never happens here.</li> | |||||
<li>I wondered if it wasn't fair to use this example theme because there's some stuff on the page that lets you switch theme styles, so I checked out actual uses of the theme (the page that advertises the theme lists users of the theme). I tried the first two listed real examples and they were both much slower than this demo page.</li> | |||||
</ul></li> | |||||
<li>Reddit | |||||
<ul> | |||||
<li>reddit.com</li> | |||||
<li>Has an unusually low <code>LCP*</code> compared to how long it takes for the page to become usable. Although not measured in this test, I generally find the page slow and sort of unusable on Intel Macbooks which are, by historical standards, extremely fast computers (unless I use old.reddit.com)</li> | |||||
</ul></li> | |||||
<li>Mastodon | |||||
<ul> | |||||
<li>mastodon.social/@danluu: used my profile where possible</li> | |||||
<li>Fails to load on <code>Itel P32</code>, just gives you a blank screen. Due to how long things generally take on the <code>Itel P32</code>, it's not obvious for a while if the page is failing or if it's just slow</li> | |||||
</ul></li> | |||||
<li>Quora | |||||
<ul> | |||||
<li>www.quora.com/Ever-felt-like-giving-up-on-your-dreams-How-did-you-come-out-of-it: I tried googling for quora + the username of a metafilter user who I've heard is now prolific on Quora. Rather than giving their profile page, Google returned this page, which appears to have nothing to do with the user I searched for. So, this isn't comparable to the social media profiles, but getting a random irrelevant Quora result from Google is how I tend to interact with Quora, so I guess this is representative of my Quora usage.</li> | |||||
<li>On the <code>Itel P32</code>, the page stops executing scripts at some point and doesn't fully load. This causes it to fail to display properly. Interacting with the page doesn't really work either.</li> | |||||
</ul></li> | |||||
<li>Substack | |||||
<ul> | |||||
<li>Used thezvi.substack.com because I know Zvi has a substack and writes about similar topics.</li> | |||||
</ul></li> | |||||
<li>vBulletin: | |||||
<ul> | |||||
<li>forum.vbulletin.com: this is what turned up when I searched for an official forum.</li> | |||||
</ul></li> | |||||
<li>Medium | |||||
<ul> | |||||
<li>medium.com/swlh: I don't read anything on Medium, so I googled for programming blogs on Medium and this was the top hit. From looking at the theme, it doesn't appear to be unusually heavy or particularly customized for a Medium blog. Since it appears to be widely read and popular, it's more likely to be served from a CDN and than some of the other blogs here.</li> | |||||
<li>On a run that wasn't a benchmark reference run, on the <code>Itel P32</code>, I tried scrolling starting 35s after loading the page. The delay to scroll was <code>5s-8s</code> and scrolling moved an unpredictable amount, making the page completely unusable. This wasn't marked as a <code>FAIL</code> in the table, but one could argue that this should be a <code>FAIL</code> since the page is unusable.</li> | |||||
</ul></li> | |||||
<li>Ghost | |||||
<ul> | |||||
<li>source.ghost.io because this is the current default Ghost theme and it was the first example I found</li> | |||||
</ul></li> | |||||
<li>Wordpress | |||||
<ul> | |||||
<li>2024.wordpress.net because this is the current default wordpress theme and this was the first example of it I found</li> | |||||
</ul></li> | |||||
<li>XenForo | |||||
<ul> | |||||
<li>xenforo.com/community/: this is what turned up when I searched for an official forum</li> | |||||
<li>On the <code>Itel P32</code>, the layout is badly wrong and page content overlaps itself. There's no reasonable way to interact with the element you want because of this, and reading the text requires reading text that's been overprinted multiple times.</li> | |||||
</ul></li> | |||||
<li>Wordpress (old) | |||||
<ul> | |||||
<li>Used thezvi.wordpress.com because it has the same content as Zvi's substack, and happens to be on some old wordpress theme that used to be a very common choice</li> | |||||
</ul></li> | |||||
<li>phpBB | |||||
<ul> | |||||
<li>www.phpbb.com/community/index.php: this is what turned up when I searched for an official forum.</li> | |||||
</ul></li> | |||||
<li>MyBB | |||||
<ul> | |||||
<li>community.mybb.com: this is what turned up when I searched for an official forum.</li> | |||||
<li>Site doesn't serve up a mobile version. In general, I find the desktop version of sites to be significantly better than the mobile version when on a slow device, so this works quite well, although they're likely penalized by Google for this.</li> | |||||
</ul></li> | |||||
<li>HN | |||||
<ul> | |||||
<li>news.ycombinator.com</li> | |||||
<li>In principle, HN should be the slowest social media site or link aggregator because it's written in a custom Lisp that isn't highly optimized and the code was originally written with brevity and cleverness in mind, which generally gives you fairly poor performance. However, that's only poor relative to what you'd get if you were writing high-performance code, which is not a relevant point of comparison here.</li> | |||||
</ul></li> | |||||
<li>danluu.com | |||||
<ul> | |||||
<li>Self explanatory</li> | |||||
<li>This currently uses a bit less CPU than HN, but I expect this to eventually use more CPU as the main page keeps growing. At the moment, this page has 176 links to 168 articles vs. HN's 199 links to 30 articles but, barring an untimely demise, this page should eventually have more links than HN. | |||||
<ul> | |||||
<li>As noted above, I find that pagination for such small pages makes the browsing experience much worse on slow devices or with bad connections, so I don't want to "optimize" this by paginating it or, even worse, doing some kind of dynamic content loading on scroll.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>Woo Commerce | |||||
<ul> | |||||
<li>I originally measured Woo Commerce as well but, unlike the pages and platforms tested above, I didn't find that being fast or slow on the initial load was necessarily representative of subsequent performance of other action, so this wasn't included in the table because having this in the table is sort of asking for a comparison against Shopify. In particular, while the "most default" Woo theme I could find was significantly faster than the "most default" Shopify theme on initial load on a slow device, performance was multidimensional enough that it was easy to find realistic scenarios where Shopify was faster than Woo and vice versa on a slow device, which is quite different from what I saw with newer blogging platforms like Substack and Medium compared to older platforms like Wordpress, or a modern forum like Discourse versus the older PHP-based forums. A real comparison of shopping sites that have carts, checkout flows, etc., would require a better understanding of real-world usage of these sites than I was going to get in a single day.</li> | |||||
</ul></li> | |||||
<li>NodeBB | |||||
<ul> | |||||
<li>community.nodebb.org</li> | |||||
<li>This wasn't in my original tests and I only tried this out because one of the founders of NodeBB suggested it, saying "I am interested in seeing whether @nodebb@fosstodon.org would fare better in your testing. We spent quite a bit of time over the years on making it wicked fast, and I personally feel it is a better representation of modern forum software than Discourse, at least on speed and initial payload."</li> | |||||
<li>I didn't do the full set of tests because I don't keep the <code>Itel P32</code> charged (the battery is in rough shape and discharges quite quickly once unplugged, so I'd have to wait quite a while to get it into a charged state)</li> | |||||
<li>On the tests I did, it got <code>0.3s/0.4s</code> on the <code>M1</code> and <code>3.4s/7.2s</code> on the <code>Tecno Spark 8C</code>. This is moderately slower than vBulletin and significantly slower than the faster php forums, but much faster than Discourse. If you need a "modern" forum for some reason and want to have your forum be usable by people who aren't, by global standards, rich, this seems like it could work.</li> | |||||
<li>Another notable thing, given that it's a "modern" site, is that interaction works fine after initial load; you can scroll and tap on things and this all basically works, nothing crashed, etc.</li> | |||||
<li>Sizes were <code>0.9 MB</code> / <code>2.2 MB</code>, so also fairly light for a "modern" site and possibly usable on a slow connection, although slow connections weren't tested here.</li> | |||||
</ul></li> | |||||
</ul> | |||||
<p>Another kind of testing would be to try to configure pages to look as similar as possible. I'd be interested in seeing that results for that if anyone does it, but that test would be much more time consuming. For one thing, it requires customizing each site. And for another, it requires deciding what sites should look like. If you test something danluu.com-like, every platform that lets you serve up something light straight out of a CDN, like Wordpress and Ghost, should score similarly, with the score being dependent on the CDN and the CDN cache hit rate. Sites like Medium and Substack, which have relatively little customizability would score pretty much as they do here. Realistically, from looking at what sites exist, most users will create sites that are slower than the "most default" themes for Wordpress and Ghost, although it's plausible that readers of this blog would, on average, do the opposite, so you'd probably want to test a variety of different site styles.</p> | |||||
<h3 id="appendix-this-site-vs-sites-that-don-t-work-on-slow-devices-or-slow-connections">Appendix: this site vs. sites that don't work on slow devices or slow connections</h3> | |||||
<p>Just as an aside, something I've found funny for a long time is that I get quite a bit of hate mail about the styling on this page (and a similar volume of appreciation mail). By hate mail, I don't mean polite suggestions to change things, I mean the equivalent of road rage, but for web browsing; web rage. I know people who run sites that are complex enough that they're unusable by a significant fraction of people in the world. How come people are so incensed about the styling of this site and, proportionally, basically don't care at all that the web is unusable for so many people?</p> | |||||
<p>Another funny thing here is that the people who appreciate the styling generally appreciate that the site doesn't override any kind of default styling, letting you make the width exactly what you want (by setting your window size how you want it) and it also doesn't override any kind of default styling you apply to sites. The people who are really insistent about this want everyone to have some width limit they prefer, some font they prefer, etc., but it's always framed in a way as if they don't want it, it's really for the benefit of people at large even though accommodating the preferences of the web ragers would directly oppose the preferences of people who prefer (just for example) to be able to adjust the text width by adjusting their window width.</p> | |||||
<p>Until I pointed this out tens of times, this iteration would usually start with web ragers telling me that "studies show" that narrower text width is objectively better, but on reading every study that exists on the topic that I could find, I didn't find this to be the case. Moreover, on asking for citations, it's clear that people saying this generally hadn't read any studies on this at all and would sometimes hastily send me a study that they did not seem to have read. When I'd point this out, people would then change their argument to how studies can't really describe the issue (odd that they'd cite studies in the first place), although one person cited a book to me (which I read and they, apparently, had not since it also didn't support their argument) and then move to how this is what everyone wants, even though that's clearly not the case, both from the comments I've gotten as well as the data I have from when I made the change.</p> | |||||
<p>Web ragers who have this line of reasoning generally can't seem to absorb the information that their preferences are not universal and will insist that they regardless of what people say they like, which I find fairly interesting. On the data, when I switched from Octopress styling (at the time, the most popular styling for programming bloggers) to the current styling, I got what appeared to be a causal increase in traffic and engagement, so it appears that not only do people who write me appreciation mail about the styling like the styling, the overall feeling of people who don't write to me appears to be that the site is fine and apparently more appealing than standard programmer blog styling. When I've noted this, people tend to become become further invested in the idea that their preferences are universal and that people who think they have other preferences are wrong and reply with total nonsense.</p> | |||||
<p>For me, two questions I'm curious about are why do people feel the need to fabricate evidence on this topic (referring to studies when they haven't read any, googling for studies and then linking to one that says the opposite of what they claim it says, presumably because they didn't really read it, etc.) in order to claim that there are "objective" reasons their preferences are universal or correct, and why are people so much more incensed by this than by the global accessibility problems caused by typical web design? On the latter, I suspect if you polled people with an abstract survey, they would rate global accessibility to be a larger problem, but by revealed preference both in terms of what people create as well as what irritates them enough to send hate mail, we can see that having fully-adjustable line width and not capping line width at their preferred length is important to do something about whereas global accessibility is not. As noted above, people who run sites that aren't accessible due to performance problems generally get little to no hate mail about this. And when I use a default Octopress install, I got zero hate mail about this. Fewer people read my site at the time, but my traffic volume hasn't increased by a huge amount since then and the amount of hate mail I get about my site design has gone from zero to a fair amount, <abbr title="To find the plausible range of underlying ratios, we can do a simple Bayesian adjustment here and we still find that the ratio of hate mail has increased by much more than the increase in traffic; maybe one can argue that hate mail for slow sites is spread across all slow sites, so a second adjustment needs to be done here?">an infinitely higher ratio</abbr> than the increase in traffic.</p> | |||||
<p>To be clear, I certainly wouldn't claim that the design on this site is optimal. <a href="https://danluu.com/octopress-speedup/">I just removed the CSS from the most popular blogging platform for programmers at the time because that CSS seemed objetively bad for people with low-end connections</a> and, as a side effect, got more traffic and engagement overall, not just from locations where people tend to have lower end connections and devices. No doubt a designer who cares about users on low-end connections and devices could do better, but there's something quite odd about both the untruthfulness and the vitriol of comments on this.</p> | |||||
</article> | |||||
<hr> | |||||
<footer> | |||||
<p> | |||||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use> | |||||
</svg> Accueil</a> • | |||||
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use> | |||||
</svg> Suivre</a> • | |||||
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use> | |||||
</svg> Pro</a> • | |||||
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use> | |||||
</svg> Email</a> • | |||||
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use> | |||||
</svg> Légal</abbr> | |||||
</p> | |||||
<template id="theme-selector"> | |||||
<form> | |||||
<fieldset> | |||||
<legend><svg class="icon icon-brightness-contrast"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use> | |||||
</svg> Thème</legend> | |||||
<label> | |||||
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto | |||||
</label> | |||||
<label> | |||||
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé | |||||
</label> | |||||
<label> | |||||
<input type="radio" value="light" name="chosen-color-scheme"> Clair | |||||
</label> | |||||
</fieldset> | |||||
</form> | |||||
</template> | |||||
</footer> | |||||
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script> | |||||
<script> | |||||
function loadThemeForm(templateName) { | |||||
const themeSelectorTemplate = document.querySelector(templateName) | |||||
const form = themeSelectorTemplate.content.firstElementChild | |||||
themeSelectorTemplate.replaceWith(form) | |||||
form.addEventListener('change', (e) => { | |||||
const chosenColorScheme = e.target.value | |||||
localStorage.setItem('theme', chosenColorScheme) | |||||
toggleTheme(chosenColorScheme) | |||||
}) | |||||
const selectedTheme = localStorage.getItem('theme') | |||||
if (selectedTheme && selectedTheme !== 'undefined') { | |||||
form.querySelector(`[value="${selectedTheme}"]`).checked = true | |||||
} | |||||
} | |||||
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' | |||||
window.addEventListener('load', () => { | |||||
let hasDarkRules = false | |||||
for (const styleSheet of Array.from(document.styleSheets)) { | |||||
let mediaRules = [] | |||||
for (const cssRule of styleSheet.cssRules) { | |||||
if (cssRule.type !== CSSRule.MEDIA_RULE) { | |||||
continue | |||||
} | |||||
// WARNING: Safari does not have/supports `conditionText`. | |||||
if (cssRule.conditionText) { | |||||
if (cssRule.conditionText !== prefersColorSchemeDark) { | |||||
continue | |||||
} | |||||
} else { | |||||
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { | |||||
continue | |||||
} | |||||
} | |||||
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) | |||||
} | |||||
// WARNING: do not try to insert a Rule to a styleSheet you are | |||||
// currently iterating on, otherwise the browser will be stuck | |||||
// in a infinite loop… | |||||
for (const mediaRule of mediaRules) { | |||||
styleSheet.insertRule(mediaRule.cssText) | |||||
hasDarkRules = true | |||||
} | |||||
} | |||||
if (hasDarkRules) { | |||||
loadThemeForm('#theme-selector') | |||||
} | |||||
}) | |||||
</script> | |||||
</body> | |||||
</html> |
title: How web bloat impacts users with slow devices | |||||
url: https://danluu.com/slow-device/ | |||||
hash_url: a988555163e09729b925dbf715ce256c | |||||
archive_date: 2024-03-25 | |||||
og_image: https://danluu.com/slow-device-performance.png | |||||
description: More of the web is becoming inaccessible to people with low-end devices even if they have high-end connections. | |||||
favicon: data:;base64,= | |||||
language: en_US | |||||
<p>In 2017, <a href="https://danluu.com/web-bloat/">we looked at how web bloat affects users with slow connections</a>. Even in the U.S., <a href="https://twitter.com/danluu/status/1116565029791260672">many users didn't have broadband speeds</a>, making much of the web difficult to use. It's still the case that many users don't have broadband speeds, both inside and outside of the U.S. and that much of the modern web isn't usable for people with slow internet, but the exponential increase in bandwidth (Nielsen suggests <abbr title="Unfortunately, I don't know of a public source for low-end data, say 10%-ile or 1%-ile; let me know if you have numbers on this">this is 50% per year for high-end connections</abbr>) has outpaced web bloat for typical sites, making this less of a problem than it was in 2017, although it's still a serious problem for people with poor connections.</p> | |||||
<p>CPU performance for web apps hasn't scaled nearly as quickly as bandwidth so, while more of the web is becoming accessible to people with low-end connections, more of the web is becoming inaccessible to people with low-end devices even if they have high-end connections. For example, if I try browsing a "modern" Discourse-powered forum on a <code>Tecno Spark 8C</code>, it sometimes crashes the browser. Between crashes, on measuring the performance, the responsiveness is significantly worse than browsing a BBS with an <code>8 MHz 286</code> and a <code>1200 baud</code> modem. On my <code>1Gbps</code> home internet connection, the <code>2.6 MB</code> compressed payload size "necessary" to load message titles is relatively light. The over-the-wire payload size has "only" increased by <code>1000x</code>, which is dwarfed by the increase in internet speeds. But the opposite is true when it comes to CPU speeds — for web browsing and forum loading performance, the <code>8-core (2 1.6 GHz Cortex-A75 / 6 1.6 GHz Cortex-A55)</code> CPU can't handle Discourse. The CPU is something like <code>100000x</code> faster than our <code>286</code>. Perhaps a <code>1000000x</code> faster device would be sufficient.</p> | |||||
<p>For anyone not familiar with the <code>Tecno Spark 8C</code>, today, a new <code>Tecno Spark 8C</code>, a quick search indicates that one can be hand for <code>USD 50-60</code> in Nigeria and perhaps <code>USD 100-110</code> in India. As <abbr title="The estimates for Nigerian median income that I looked at seem good enough, but the Indian estimate I found was a bit iffier; if you have a good source for Indian income distribution, please pass it along.">a fraction of median household income, that's substantially more than a current generation iPhone in the U.S. today.</abbr></p> | |||||
<p>By worldwide standards, the <code>Tecno Spark 8C</code> isn't even close to being a low-end device, so we'll also look at performance on an <code>Itel P32</code>, which is a lower end device (though still far from the lowest-end device people are using today). Additionally, we'll look at performance with an <code>M3 Max Macbook (14-core)</code>, an <code>M1 Pro Macbook (8-core)</code>, and the <code>M3 Max</code> set to <code>10x</code> throttling in Chrome dev tools. In order to give these devices every advantage, we'll be on fairly high-speed internet (1Gbps, with a WiFi router that's benchmarked as having lower latency under load than most of its peers). We'll look at some blogging platforms and micro-blogging platforms (this blog, Substack, Medium, Ghost, Hugo, Tumblr, Mastodon, Twitter, Threads, Bluesky, Patreon), forum platforms (Discourse, Reddit, Quora, vBulletin, XenForo, phpBB, and myBB), and platforms commonly used by small businesses (Wix, Squarespace, Shopify, and WordPress again).</p> | |||||
<p>In the table below, every row represents a website and every non-label column is a metric. After the website name column, we have the compressed size transferred over the wire (<code>wire</code>) and the raw, uncompressed, size (<code>raw</code>). Then we have, for each device, Largest Contentful Paint* (<code>LCP*</code>) and CPU usage on the main thread (<code>CPU</code>). Google's docs explain <code>LCP</code> as</p> | |||||
<blockquote> | |||||
<p>Largest Contentful Paint (LCP) measures when a user perceives that the largest content of a page is visible. The metric value for LCP represents the time duration between the user initiating the page load and the page rendering its primary content</p> | |||||
</blockquote> | |||||
<p><code>LCP</code> is a common optimization target because it's presented as one of the primary metrics in Google PageSpeed Insights, a "Core Web Vital" metric. There's an asterisk next to <code>LCP</code> as used in this document because, <code>LCP</code> as measured by Chrome is about painting a large fraction of the screen, as opposed to the definition above, which is about content. As sites have optimized for <code>LCP</code>, it's not uncommon to have a large paint (update) that's completely useless to the user, with the actual content of the page appearing well after the <code>LCP</code>. In cases where that happens, I've used the timestamp when useful content appears, not the <code>LCP</code> as defined by when a large but useless update occurs. The full details of the tests and why these metrics were chosen are discussed in an appendix.</p> | |||||
<p>Although CPU time isn't a "Core Web Vital", it's presented here because it's a simple metric that's highly correlated with my and other users' perception of usability on slow devices. See appendix for more detailed discussion on this. One reason CPU time works as a metric is that, if a page has great numbers for all other metrics but uses a ton of CPU time, the page is not going to be usable on a slow device. If it takes 100% CPU for 30 seconds, the page will be completely unusable for 30 seconds, and if it takes 50% CPU for 60 seconds, the page will be barely usable for 60 seconds, etc. Another reason it works is that, relative to commonly used metrics, it's hard to cheat on CPU time and make optimizations that significantly move the number without impacting user experience.</p> | |||||
<p>The color scheme in the table below is that, for sizes, more green = smaller / fast and more red = larger / slower. Extreme values are in black.</p> | |||||
<p> | |||||
</p><table> | |||||
<tr> | |||||
<th rowspan="2">Site</th><th colspan="2">Size</th><th colspan="2">M3 Max</th><th colspan="2">M1 Pro</th><th colspan="2">M3/10</th><th colspan="2">Tecno S8C</th><th colspan="2">Itel P32</th></tr> | |||||
<tr> | |||||
<th>wire</th><th>raw</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th><th>LCP*</th><th>CPU</th></tr> | |||||
<tr> | |||||
<td class="l">danluu.com</td><td><font>6kB</font></td><td><font>18kB</font></td><td><font>50ms</font></td><td><font>20ms</font></td><td><font>50ms</font></td><td><font>30ms</font></td><td><font>0.2s</font></td><td><font>0.3s</font></td><td>0.4s</td><td><font>0.3s</font></td><td>0.5s</td><td>0.5s</td></tr> | |||||
<tr> | |||||
<td class="l">HN</td><td><font>11kB</font></td><td><font>50kB</font></td><td><font>0.1s</font></td><td><font>30ms</font></td><td><font>0.1s</font></td><td><font>30ms</font></td><td><font>0.3s</font></td><td><font>0.3s</font></td><td>0.5s</td><td>0.5s</td><td>0.7s</td><td>0.6s</td></tr> | |||||
<tr> | |||||
<td class="l">MyBB</td><td><font>0.1MB</font></td><td><font>0.3MB</font></td><td><font>0.3s</font></td><td><font>0.1s</font></td><td><font>0.3s</font></td><td><font>0.1s</font></td><td>0.6s</td><td>0.6s</td><td>0.8s</td><td>0.8s</td><td>2.1s</td><td>1.9s</td></tr> | |||||
<tr> | |||||
<td class="l">phpBB</td><td>0.4MB</td><td>0.9MB</td><td><font>0.3s</font></td><td><font>0.1s</font></td><td>0.4s</td><td><font>0.1s</font></td><td>0.7s</td><td>1.1s</td><td>1.7s</td><td>1.5s</td><td>4.1s</td><td>3.9s</td></tr> | |||||
<tr> | |||||
<td class="l">WordPress</td><td>1.4MB</td><td>1.7MB</td><td><font>0.2s</font></td><td><font>60ms</font></td><td><font>0.2s</font></td><td><font>80ms</font></td><td>0.7s</td><td>0.7s</td><td>1s</td><td>1.5s</td><td>1.2s</td><td>2.5s</td></tr> | |||||
<tr> | |||||
<td class="l">WordPress (old)</td><td>0.3MB</td><td>1.0MB</td><td><font>80ms</font></td><td><font>70ms</font></td><td><font>90ms</font></td><td><font>90ms</font></td><td>0.4s</td><td>0.9s</td><td>0.7s</td><td>1.7s</td><td>1.1s</td><td>1.9s</td></tr> | |||||
<tr> | |||||
<td class="l">XenForo</td><td>0.3MB</td><td>1.0MB</td><td>0.4s</td><td><font>0.1s</font></td><td>0.6s</td><td><font>0.2s</font></td><td>1.4s</td><td>1.5s</td><td>1.5s</td><td>1.8s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Ghost</td><td>0.7MB</td><td>2.4MB</td><td><font>0.1s</font></td><td><font>0.2s</font></td><td><font>0.2s</font></td><td><font>0.2s</font></td><td>1.1s</td><td>2.2s</td><td>1s</td><td>2.4s</td><td>1.1s</td><td>3.5s</td></tr> | |||||
<tr> | |||||
<td class="l">vBulletin</td><td>1.2MB</td><td>3.4MB</td><td>0.5s</td><td><font>0.2s</font></td><td>0.6s</td><td><font>0.3s</font></td><td>1.1s</td><td>2.9s</td><td>4.4s</td><td>4.8s</td><td>13s</td><td>16s</td></tr> | |||||
<tr> | |||||
<td class="l">Squarespace</td><td>1.9MB</td><td>7.1MB</td><td><font>0.1s</font></td><td>0.4s</td><td><font>0.2s</font></td><td>0.4s</td><td>0.7s</td><td>3.6s</td><td>14s</td><td>5.1s</td><td>16s</td><td>19s</td></tr> | |||||
<tr> | |||||
<td class="l">Mastodon</td><td>3.8MB</td><td>5.3MB</td><td><font>0.2s</font></td><td><font>0.3s</font></td><td><font>0.2s</font></td><td>0.4s</td><td>1.8s</td><td>4.7s</td><td>2.0s</td><td>7.6s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Tumblr</td><td>3.5MB</td><td>7.1MB</td><td>0.7s</td><td>0.6s</td><td>1.1s</td><td>0.7s</td><td>1.0s</td><td>7.0s</td><td>14s</td><td>7.9s</td><td>8.7s</td><td>8.7s</td></tr> | |||||
<tr> | |||||
<td class="l">Quora</td><td>0.6MB</td><td>4.9MB</td><td>0.7s</td><td>1.2s</td><td>0.8s</td><td>1.3s</td><td>2.6s</td><td>8.7s</td><td><font>FAIL</font></td><td><font>FAIL</font></td><td>19s</td><td><font>29s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Bluesky</td><td>4.8MB</td><td>10MB</td><td>1.0s</td><td>0.4s</td><td>1.0s</td><td>0.5s</td><td>5.1s</td><td>6.0s</td><td>8.1s</td><td>8.3s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Wix</td><td>7.0MB</td><td>21MB</td><td>2.4s</td><td>1.1s</td><td>2.5s</td><td>1.2s</td><td>18s</td><td>11s</td><td>5.6s</td><td>10s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Substack</td><td>1.3MB</td><td>4.3MB</td><td>0.4s</td><td>0.5s</td><td>0.4s</td><td>0.5s</td><td>1.5s</td><td>4.9s</td><td>14s</td><td>14s</td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Threads</td><td>9.3MB</td><td>13MB</td><td>1.5s</td><td>0.5s</td><td>1.6s</td><td>0.7s</td><td>5.1s</td><td>6.1s</td><td>6.4s</td><td>16s</td><td><font>28s</font></td><td><font>66s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Twitter</td><td>4.7MB</td><td>11MB</td><td>2.6s</td><td>0.9s</td><td>2.7s</td><td>1.1s</td><td>5.6s</td><td>6.6s</td><td>12s</td><td>19s</td><td>24s</td><td><font>43s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Shopify</td><td>3.0MB</td><td>5.5MB</td><td>0.4s</td><td><font>0.2s</font></td><td>0.4s</td><td><font>0.3s</font></td><td>0.7s</td><td>2.3s</td><td>10s</td><td><font>26s</font></td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Discourse</td><td>2.6MB</td><td>10MB</td><td>1.1s</td><td>0.5s</td><td>1.5s</td><td>0.6s</td><td>6.5s</td><td>5.9s</td><td>15s</td><td><font>26s</font></td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
<tr> | |||||
<td class="l">Patreon</td><td>4.0MB</td><td>13MB</td><td>0.6s</td><td>1.0s</td><td>1.2s</td><td>1.2s</td><td>1.2s</td><td>14s</td><td>1.7s</td><td><font>31s</font></td><td>9.1s</td><td><font>45s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Medium</td><td>1.2MB</td><td>3.3MB</td><td>1.4s</td><td>0.7s</td><td>1.4s</td><td>1s</td><td>2s</td><td>11s</td><td>2.8s</td><td><font>33s</font></td><td>3.2s</td><td><font>63s</font></td></tr> | |||||
<tr> | |||||
<td class="l">Reddit</td><td>1.7MB</td><td>5.4MB</td><td>0.9s</td><td>0.7s</td><td>0.9s</td><td>0.9s</td><td>6.2s</td><td>12s</td><td>1.2s</td><td><font>∞</font></td><td><font>FAIL</font></td><td><font>FAIL</font></td></tr> | |||||
</table> | |||||
<p>At a first glance, the table seems about right, in that the sites that feel slow unless you have a super fast device show up as slow in the table (as in, <code>max(LCP*,CPU))</code> is high on lower-end devices). When I polled folks about what platforms they thought would be fastest and slowest on our slow devices (<a href="https://mastodon.social/@danluu/111994437263038931">Mastodon</a>, <a href="https://twitter.com/danluu/status/1761875263359537652">Twitter</a>, <a href="https://www.threads.net/@danluu.danluu/post/C3yVpfKS-RP">Threads</a>), they generally correctly predicted that Wordpress and Ghost and Wordpress would be faster than Substack and Medium, and that Discourse would be much slower than old PHP forums like phpBB, XenForo, and vBulletin. I also pulled Google PageSpeed Insights (PSI) scores for pages (not shown) and the correlation isn't as strong with those numbers <abbr title="For the 'real world' numbers, this is also because users with slow devices can't really use some of these sites, so their devices aren't counted in the distribution and PSI doesn't normalize for this.">because</abbr> a handful of sites have managed to optimize their PSI scores without actually speeding up their pages for users.</p> | |||||
<p>If you've never used a low-end device like this, the general experience is that many sites are unusable on the device and loading anything resource intensive (an app or a huge website) can cause crashes. Doing something too intense in a resource intensive app can also cause crashes. While <a href="https://www.youtube.com/watch?v=U1JMRFQWK70">reviews note</a> that <a href="https://www.youtube.com/watch?v=McawfNlydqk">you can run PUBG and other 3D games with decent performance</a> on a <code>Tecno Spark 8C</code>, this doesn't mean that the device is fast enough to read posts on modern text-centric social media platforms or modern text-centric web forums. While <code>40fps</code> is achievable in PUBG, we can easily see less than <code>0.4fps</code> when scrolling on these sites.</p> | |||||
<p>We can see from the table how many of the sites are unusable if you have a slow device. All of the pages with <code>10s+ CPU</code> are a fairly bad experience even after the page loads. Scrolling is very jerky, frequently dropping to a few frames per second and sometimes well below. When we tap on any link, the delay is so long that we can't be sure if our tap actually worked. If we tap again, we can get the dreaded situation where the first tap registers, which then causes the second tap to do the wrong thing, but if we wait, we often end up waiting too long because the original tap didn't actually register (or it registered, but not where we thought it did). Although MyBB doesn't serve up a mobile site and is penalized by Google for not having a mobile friendly page, it's actually much more usable on these slow mobiles than all but the fastest sites because scrolling and tapping actually work.</p> | |||||
<p>Another thing we can see is how much variance there is in the relative performance on different devices. For example, comparing an <code>M3/10</code> and a <code>Tecno Spark 8C</code>, for danluu.com and Ghost, an <code>M3/10</code> gives a halfway decent approximation of the <code>Tecno Spark 8C</code> (although danluu.com loads much too quickly), but the <code>Tecno Spark 8C</code> is about three times slower (<code>CPU</code>) for Medium, Substack, and Twitter, roughly four times slower for Reddit and Discourse, and over an order of magnitude faster for Shopify. For Wix, the <code>CPU</code> approximation is about accurate, but our `<code>Tecno Spark 8C</code> is more than 3 times slower on <code>LCP*</code>. It's great that Chrome lets you conveniently simulate a slower device from the convenience of your computer, but just enabling Chrome's CPU throttling (or using any combination of out-of-the-box options that are available) gives fairly different results than we get on many real devices. The full reasons for this are beyond the scope of the post; for the purposes of this post, it's sufficient to note that slow pages are often super-linearly slow as devices get slower and that slowness on one page doesn't strongly predict slowness on another page.</p> | |||||
<p>If take a site-centric view instead of a device-centric view, another way to look at it is that sites like Discourse, Medium, and Reddit, don't use all that much CPU on our fast <code>M3</code> and <code>M1</code> computers, but they're among the slowest on our <code>Tecno Spark 8C</code> (Reddit's CPU is shown as <code>∞</code> because, no matter how long we wait with no interaction, Reddit uses <code>~90% CPU</code>). Discourse also sometimes crashed the browser after interacting a bit or just waiting a while. For example, one time, the browser crashed after loading Discourse, scrolling twice, and then leaving the device still for a minute or two. For consistency's sake, this wasn't marked as <code>FAIL</code> in the table since the page did load but, realistically, having a page so resource intensive that the browser crashes is a significantly worse user experience than any of the <code>FAIL</code> cases in the table. When we looked at how <a href="https://danluu.com/web-bloat/">web bloat impacts users with slow connections</a>, we found that <abbr title="One thing to keep in mind here is that having a slow device and a slow connection have multiplicative impacts.">much of the web was unusable for people with slow connections and slow devices are no different</abbr>.</p> | |||||
<p>Another pattern we can see is how the older sites are, in general, faster than the newer ones, with sites that (visually) look like they haven't been updated in a decade or two tending to be among the fastest. For example, MyBB, the least modernized and oldest looking forum is <code>3.6x / 5x faster (LCP* / CPU)</code> than Discourse on the <code>M3</code>, but on the <code>Tecno Spark 8C</code>, the difference is <code>19x / 33x</code> and, given the overall scaling, it seems safe to guess that the difference would be even larger on the Itel P32 if Discourse worked on such a cheap device.</p> | |||||
<p>Another example is Wordpress (old) vs. newer, trendier, blogging platforms like Medium and Substack. Wordpress (old) is is <code>17.5x / 10x faster (LCP* / CPU)</code> than Medium and <code>5x / 7x faster (LCP* / CPU)</code> faster than Substack on our <code>M3 Max</code>, and <code>4x / 19x</code> and <code>20x / 8x</code> faster, respectively, on our <code>Tecno Spark 8C</code>. Ghost is a notable exception to this, being a modern platform (launched a year after Medium) that's competitive with older platforms (modern Wordpress is also arguably an exception, but many folks would probably still consider that to be an old platform). Among forums, NodeBB also seems to be a bit of an exception (see appendix for details).</p> | |||||
<p>Sites that use modern techniques like partially loading the page and then dynamically loading the rest of it, such as Discourse, Reddit, and Substack, tend to be less usable than the scores in the table indicate. Although, in principle, you could build such a site in a simple way that works well with cheap devices but, in practice sites that use dynamic loading tend to be complex enough that the sites are extremely janky on low-end devices. It's generally difficult or impossible to scroll a predictable distance, which means that users will sometimes accidentally trigger more loading by scrolling too far, causing the page to lock up. Many pages actually remove the parts of the page you scrolled past as you scroll; all such pages are essentially unusable. Other basic web features, like page search, also generally stop working. Pages with this kind of dynamic loading can't rely on the simple and fast ctrl/command+F search and have to build their own search. How well this works varies (this used to work quite well in Google docs, but for the past few months or maybe a year, it takes so long to load that I have to deliberately wait after opening a doc to avoid triggering the browser's useless built in search; Discourse search has never really worked on slow devices or even not very fast but not particular slow devices).</p> | |||||
<p>In principle, these modern pages that burn a ton of CPU when loading could be doing pre-work that means that later interactions on the page are faster and cheaper than on the pages that do less up-front work (this is a common argument in favor of these kinds of pages), but that's not the case for pages tested, which are slower to load initially, slower on subsequent loads, and slower after they've loaded.</p> | |||||
<p>To understand why the theoretical idea that doing all this work up-front doesn't generally result in a faster experience later, this exchange between a distinguished engineer at Google and one of the founders of Discourse (and CEO at the time) is <abbr title="the founder has made similar comments elsewhere as well, so this isn't a one-off analogy for him, nor do I find it to be an unusual line of thinking in general">illustrative</abbr>, in <a href="https://danluu.com/jeff-atwood-trashes-qualcomm-engineering.png">a discussion where the founder of Discourse says that you should test mobile sites on laptops with throttled bandwidth but not throttled CPU</a>:</p> | |||||
<ul> | |||||
<li><b>Google</b>: *you* also don't have slow 3G. These two settings go together. Empathy needs to extend beyond iPhone XS users in a tunnel.</li> | |||||
<li><b>Discourse</b>: Literally any phone of vintage iPhone 6 or greater is basically as fast as the "average" laptop. You have to understand how brutally bad Qualcomm is at their job. Look it up if you don't believe me.</li> | |||||
<li><b>Google</b>: I don't need to believe you. I know. This is well known by people who care. | |||||
My point was that just like not everyone has a fast connection not everyone has a fast phone. Certainly the iPhone 6 is frequently very CPU bound on real world websites. But that isn't the point.</li> | |||||
<li><b>Discourse</b>: we've been trending towards infinite CPU speed for decades now (and we've been asymptotically there for ~5 years on desktop), what we are not and will never trend towards is infinite bandwidth. Optimize for the things that matter. and I have zero empathy for @qualcomm. Fuck Qualcomm, they're terrible at their jobs. I hope they go out of business and the ground their company existed on is plowed with salt so nothing can ever grow there again.</li> | |||||
<li><b>Google</b>: Mobile devices are not at all bandwidth constraint in most circumstances. They are latency constraint. Even the latest iPhone is CPU constraint before it is bandwidth constraint. If you do well on 4x slow down on a MBP things are pretty alright</li> | |||||
<li>...</li> | |||||
<li><b>Google</b>: Are 100% of users on iOS?</li> | |||||
<li><b>Discourse</b>: The influential users who spend money tend to be, I’ll tell you that ... Pointless to worry about cpu, it is effectively infinite already on iOS, and even with Qualcomm’s incompetence, will be within 4 more years on their embarrassing SoCs as well</li> | |||||
</ul> | |||||
<p>When someone asks the founder of Discourse, "just wondering why you hate them", he responds with a link that cites the Kraken and Octane benchmarks from <a href="https://www.anandtech.com/show/9146/the-samsung-galaxy-s6-and-s6-edge-review/5">this Anandtech review</a>, which have the Qualcomm chip at 74% and 85% of the performance of the then-current Apple chip, respectively.</p> | |||||
<p>The founder and then-CEO of Discourse considers Qualcomm's mobile performance embarrassing and finds this so offensive that he thinks Qualcomm engineers should all lose their jobs for delivering <abbr title="I think it could be reasonable to cite a lower number, but I'm using the number he cited, not what I would cite">74% to 85% of the performance of Apple</abbr>. Apple has what I consider to be an all-time great performance team. Reasonable people could disagree on that, but one has to at least think of them as a world-class team. So, producing a product with <abbr title="recall that, on a Tecno Spark 8, Discourse is 33 times slower than MyBB, which isn't particularly optimized for performance">74% to 85% of an all-time-great team is considered an embarrassment worthy of losing your job</abbr>.</p> | |||||
<p>There are two attitudes on display here which I see in a lot of software folks. First, that CPU speed is infinite and one shouldn't worry about CPU optimization. And second, that gigantic speedups from hardware should be expected and the only reason hardware engineers wouldn't achieve them is due to spectacular incompetence, so the slow software should be blamed on hardware engineers, not software engineers. Donald Knuth expressed a similar sentiment in</p> | |||||
<blockquote> | |||||
<p>I might as well flame a bit about my personal unhappiness with the current trend toward multicore architecture. To me, it looks more or less like the hardware designers have run out of ideas, and that they’re trying to pass the blame for the future demise of Moore’s Law to the software writers by giving us machines that work faster only on a few key benchmarks! I won’t be surprised at all if the whole multiithreading idea turns out to be a flop, worse than the "Itanium" approach that was supposed to be so terrific—until it turned out that the wished-for compilers were basically impossible to write. Let me put it this way: During the past 50 years, I’ve written well over a thousand programs, many of which have substantial size. I can’t think of even five of those programs that would have been enhanced noticeably by parallelism or multithreading. Surely, for example, multiple processors are no help to TeX ... I know that important applications for parallelism exist—rendering graphics, breaking codes, scanning images, simulating physical and biological processes, etc. But all these applications require dedicated code and special-purpose techniques, which will need to be changed substantially every few years. Even if I knew enough about such methods to write about them in TAOCP, my time would be largely wasted, because soon there would be little reason for anybody to read those parts ... The machine I use today has dual processors. I get to use them both only when I’m running two independent jobs at the same time; that’s nice, but it happens only a few minutes every week.</p> | |||||
</blockquote> | |||||
<p>In the case of Discourse, a hardware engineer is an embarrassment not deserving of a job if they can't hit 90% of the performance of an all-time-great performance team but, as a software engineer, delivering 3% the performance of a non-highly-optimized application like MyBB is no problem. In Knuth's case, <abbr title="using this term loosely, to include materials scientists, etc., which is consistent with Knuth's comments">hardware engineers</abbr> gave programmers a 100x performance increase every decade for decades with little to no work on the part of programmers. The moment this slowed down and programmers had to adapt to take advantage of new hardware, hardware engineers were "all out of ideas", but learning a few "new" (1970s and 1980s era) ideas to take advantage of current hardware would be a waste of time. And <a href="https://www.patreon.com/posts/54329188">we've previously discussed Alan Kay's claim that hardware engineers are "unsophisticated" and "uneducated" and aren't doing "real engineering" and how we'd get a 1000x speedup if we listened to Alan Kay's "sophisticated" ideas</a>.</p> | |||||
<p>It's fairly common for programmers to expect that hardware will solve all their problems, and then, when that doesn't happen, pass the issue onto the user, explaining why the programmer needn't do anything to help the user. A question one might ask is how much performance improvement programmers have given us. There are cases of algorithmic improvements that result in massive speedups but, as we noted above, Discourse, the fastest growing forum software today, seems to have given us an approximately <code>1000000x</code> slowdown in performance.</p> | |||||
<p>Another common attitude on display above is the idea that users who aren't wealthy don't matter. When asked if 100% of users are on iOS, the founder of Discourse says "The influential users who spend money tend to be, I’ll tell you that". We see the same attitude all over comments on <a href="https://tonsky.me/blog/js-bloat/">Tonsky's JavaScript Bloat post</a>, with people expressing <a href="https://danluu.com/cocktail-ideas/">cocktail-party sentiments</a> like "Phone apps are hundreds of megs, why are we obsessing over web apps that are a few megs? Starving children in Africa can download Android apps but not web apps? Come on" and "surely no user of gitlab would be poor enough to have a slow device, let's be serious" (paraphrased for length).</p> | |||||
<p>But when we look at the size of apps that are downloaded in Africa, we see that people who aren't on high-end devices use apps like Facebook Lite (a couple megs) and commonly use apps that are a single digit to low double digit number of megabytes. There are multiple reasons app makers care about their app size. One is just the total storage available on the phone; if you watch real users install apps, they often have to delete and uninstall things to put a new app on, so the smaller size is both easier to to install and has a lower chance of being uninstalled when the user is looking for more space. Another is that, if you look at data on app size and usage (I don't know of any public data on this; please pass it along if you have something public I can reference), when large apps increase the size and memory usage, they get more crashes, which drives down user retention, growth, and engagement and, conversely, when they optimize their size and memory usage, they get fewer crashes and better user retention, growth, and engagement.</p> | |||||
<p><a href="https://infrequently.org/2024/01/performance-inequality-gap-2024/">Alex Russell points out that iOS has 7% market share in India (a 1.4B person market) and 6% market share in Latin America (a 600M person market)</a>. Although the founder of Discourse says that these aren't "influential users" who matter, these are still real human beings. Alex further points out that, according to Windows telemetry, which covers the vast majority of desktop users, most laptop/desktop users are on low-end machines which are likely slower than a modern iPhone.</p> | |||||
<p>On the bit about no programmers having slow devices, I know plenty of people who are using hand-me-down devices that are old and slow. Many of them aren't even really poor; they just don't see why (for example) their kid needs a super fast device, and they don't understand how much of the modern web works poorly on slow devices. After all, the "slow" device can play 3d games and (with the right OS) compile codebases like Linux or Chromium, so why shouldn't the device be able to interact with a site like gitlab?</p> | |||||
<p>Contrary to the claim from the founder of Discourse that, within years, every Android user will be on some kind of super fast Android device, it's been six years since his comment and it's going to be at least a decade before almost everyone in the world who's using a phone has a high-speed device and this could easily take two decades or more. If you look up marketshare stats for Discourse, it's extremely successful; it appears to be the fastest growing forum software in the world by a large margin. The impact of having the fastest growing forum software in the world created by an organization whose then-leader was willing to state that he doesn't really care about users who aren't "influential users who spend money", who don't have access to "infinite CPU speed", is that a lot of forums are now inaccessible to people who don't have enough wealth to buy a device with effectively infinite CPU.</p> | |||||
<p>If the founder of Discourse were an anomaly, this wouldn't be too much of a problem, but he's just verbalizing the implicit assumptions a lot of programmers have, which is why we see that so many modern websites are unusable if you buy the income-adjusted equivalent of a new, current generation, iPhone in a low-income country.</p> | |||||
<p><i>Thanks to Yossi Kreinen, Fabian Giesen, John O'Nolan, Joseph Scott, Loren McIntyre, Daniel Filan, @acidshill, Alex Russell, Chris Adams, Tobias Marschner, Matt Stuchlik, @gekitsu@toot.cat, Justin Blank, Andy Kelley, Julian Lam, Matthew Thomas, avarcat, @eamon@social.coop, and David Turner for comments/corrections/discussion.</i></p> | |||||
<h3 id="appendix-gaming-lcp">Appendix: gaming LCP</h3> | |||||
<p>We noted above that we used <code>LCP*</code> and not <code>LCP</code>. This is because <code>LCP</code> basically measures when the largest change happens. When this metric was not deliberately gamed in ways that don't benefit the user, this was a great metric, but this metric has become less representative of the actual user experience as more people have gamed it. In the less blatant cases, people do small optimizations that improve <code>LCP</code> but barely improve or don't improve the actual user experience.</p> | |||||
<p>In the more blatant cases, developers will deliberately flash a very large change on the page as soon as possible, generally a loading screen that has no value to the user (actually negative value because doing this increases the total amount of work done and the total time it takes to load the page) and then they carefully avoid making any change large enough that any later change would get marked as the <code>LCP</code>.</p> | |||||
<p>For the same reason <a href="https://en.wikipedia.org/wiki/Volkswagen_emissions_scandal">that VW didn't publicly discuss how it was gaming its emissions numbers</a>, developers tend to shy away from discussing this kind of <code>LCP</code> optimization in public. An exception to this is Discourse, where <a href="https://meta.discourse.org/t/introducing-discourse-splash-a-visual-preloader-displayed-while-site-assets-load/232003" rel="nofollow">they publicly announced this kind of <code>LCP</code> optimization, with comments from their devs and the then-CTO (now CEO)</a>, noting that their new "Discourse Splash" feature hugely reduced <code>LCP</code> for sites after they deployed it. And then developers ask why their <code>LCP</code> is high, the standard advice from Discourse developers is to keep elements smaller than the "Discourse Splash", so that the <code>LCP</code> timestamp is computed from this useless element that's thrown up to optimize <code>LCP</code>, as opposed to having the timestamp be computed from any actual element that's relevant to the user. <a href="https://meta.discourse.org/t/theme-components-and-largest-contentful-paint-lcp/258680" rel="nofollow">Here's a typical, official, comment from Discourse</a></p> | |||||
<blockquote> | |||||
<p>If your banner is larger than the element we use for the "Introducing Discourse Splash - A visual preloader displayed while site assets load" you gonna have a bad time for LCP.</p> | |||||
</blockquote> | |||||
<p>The official response from Discourse is that you should make sure that your content doesn't trigger the <code>LCP</code> measurement and that, instead, our loading animation timestamp is what's used to compute `LCP.</p> | |||||
<p>The sites with the most extreme ratio of <code>LCP</code> of useful content vs. Chrome's measured <code>LCP</code> were:</p> | |||||
<ul> | |||||
<li>Wix | |||||
<ul> | |||||
<li><code>M3</code>: <code>6</code></li> | |||||
<li><code>M1</code>: <code>12</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>3</code></li> | |||||
<li><code>Itel P32</code>: <code>N/A</code> <code>(FAIL)</code></li> | |||||
</ul></li> | |||||
<li>Discourse: | |||||
<ul> | |||||
<li><code>M3</code>: <code>10</code></li> | |||||
<li><code>M1</code>: <code>12</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>4</code></li> | |||||
<li><code>Itel P32</code>: <code>N/A</code> <code>(FAIL)</code></li> | |||||
</ul></li> | |||||
</ul> | |||||
<p>Although we haven't discussed the gaming of other metrics, it appears that some websites also game other metrics and "optimize" them even when this has no benefit to users.</p> | |||||
<h3 id="appendix-the-selfish-argument-for-optimizing-sites">Appendix: the selfish argument for optimizing sites</h3> | |||||
<p>This will depend on the scale of the site as well as its performance, but when I've looked at this data for large companies I've worked for, improving site and app performance is worth a mind boggling amount of money. It's measurable in A/B tests and it's also among the interventions that has, in <abbr title="where you keep a fraction of users on the old arm of the A/B test for a long duration, sometimes a year or more, in order to see the long-term impact of a change">long-term holdbacks</abbr>, a relatively large impact on growth and retention (many interventions test well but don't look as good long term, whereas performance improvements tend to look better long term).</p> | |||||
<p>Of course you can see this from the direct numbers, but you can also implicitly see this in a lot of ways when looking at the data. One angle is that (just for example), at Twitter, user-observed p99 latency was about <code>60s</code> in India as well as a number of African countries (even excluding relatively wealthy ones like Egypt and South Africa) and also about <code>60s</code> in the United States. Of course, across the entire population, people have faster devices and connections in the United States, but in every country, there are enough users that have slow devices or connections that the limiting factor is really user patience and not the underlying population-level distribution of devices and connections. Even if you don't care about users in Nigeria or India and only care about U.S. ad revenue, improving performance for low-end devices and connections has enough of impact that we could easily see the impact in global as well as U.S. revenue in A/B tests, especially in long-term holdbacks. And you also see the impact among users who have fast devices since a change that improves the latency for a user with a "low-end" device from <code>60s</code> to <code>50s</code> might improve the latency for a user with a high-end device from <code>5s</code> to <code>4.5s</code>, which has an impact on revenue, growth, and retention numbers as well.</p> | |||||
<p>For <a href="https://danluu.com/bad-decisions/">a variety of reasons that are beyond the scope of this doc</a>, this kind of boring, quantifiable, growth and revenue driving work has been difficult to get funded at most large companies I've worked for relative to flash product work that ends up showing little to no impact in long-term holdbacks.</p> | |||||
<h3 id="appendix-designing-for-low-performance-devices">Appendix: designing for low performance devices</h3> | |||||
<p>When using slow devices or any device with low bandwidth and/or poor connectivity, the best experiences, by far, are generally the ones that load a lot of content at once into a static page. If the images have proper width and height attributes and alt text, that's very helpful. Progressive images (as in progressive jpeg) isn't particularly helpful.</p> | |||||
<p>On a slow device with high bandwidth, any lightweight, static, page works well, and lightweight dynamic pages can work well if designed for performance. Heavy, dynamic, pages are doomed unless the page weight doesn't cause the page to be complex.</p> | |||||
<p>With low bandwidth and/or poor connectivity, lightweight pages are fine. With heavy pages, the best experience I've had is when I trigger a page load, go do something else, and then come back when it's done (or at least the HTML and CSS are done). I can then open each link I might want to read in a new tab, and then do something else while I wait for those to load.</p> | |||||
<p>A lot of the optimizations that modern websites do, such as partial loading that causes more loading when you scroll down the page, and the concomitant hijacking of search (because the browser's built in search is useless if the page isn't fully loaded) causes the interaction model that works to stop working and makes pages very painful to interact with.</p> | |||||
<p>Just for example, a number of people have noted that Substack performs poorly for them because it does partial page loads. <a href="https://danluu.com/substack.mp4">Here's a video by @acidshill of what it looks like to load a Substack article and then scroll on an iPhone 8</a>, where the post has a fairly fast <code>LCP</code>, but if you want to scroll past the header, you have to wait <code>6s</code> for the next page to load, and then on scrolling again, you have to wait maybe another <code>1s</code> to <code>2s</code>:</p> | |||||
<p>As an example of the opposite approach, I tried loading some fairly large plain HTML pages, such as <a href="https://danluu.com/diseconomies-scale/">https://danluu.com/diseconomies-scale/</a> (<code>0.1 MB wire</code> / <code>0.4 MB raw</code>) and <a href="https://danluu.com/threads-faq/">https://danluu.com/threads-faq/</a> (<code>0.4 MB wire</code> / <code>1.1 MB raw</code>) and these were still quite usable for me even on slow devices. <code>1.1 MB</code> seems to be larger than optimal and breaking that into a few different pages would be better on a low-end devices, but a single page with <code>1.1 MB</code> of text works much better than most modern sites on a slow device. While you can get into trouble with HTML pages that are so large that browsers can't really handle them, for pages with a normal amount of content, it generally isn't until you have <a href="https://nolanlawson.com/2023/01/17/my-talk-on-css-runtime-performance/">complex CSS payloads</a> or JS that the pages start causing problems for slow devices. Below, we test pages that are relatively simple, some of which have a fair amount of media (<code>14 MB</code> in one case) and find that these pages work ok, as long as they stay simple.</p> | |||||
<p>Chris Adams has also noted that blind users, using screen readers, often report that dynamic loading makes the experience much worse for them. Like dynamic loading to improve performance, while this can be done well, it's often either done badly or bundled with so much other complexity that the result is worse than a simple page.</p> | |||||
<p>@Qingcharles noted another accessibility issue — the (prison) parolees he works with are given "lifeline" phones, which are often very low end devices. From a quick search, in 2024, some people will get an iPhone 6 or an iPhone 8, but there are also plenty of devices that are lower end than an Itel P32, let alone a Tecno Spark 8C. They also get plans with highly limited data, and then when they run out, some people "can't fill out any forms for jobs, welfare, or navigate anywhere with Maps".</p> | |||||
<p>For sites that do up-front work and actually give you a decent experience on low end devices, Andy Kelley pointed out an example of a site that does up front work that seems to work ok on a slow device (although it would struggle on a very slow connection), <a href="https://ziglang.org/documentation/master/std/">the Zig standard library documentation</a>:</p> | |||||
<blockquote> | |||||
<p>I made the controversial decision to have it fetch all the source code up front and then do all the content rendering locally. In theory, this is CPU intensive but in practice... even those old phones have really fast CPUs!</p> | |||||
</blockquote> | |||||
<p>On the <code>Tecno Spark 8C</code>, this uses <code>4.7s</code> of CPU and, afterwards, is fairly responsive (relative to the device — <a href="https://danluu.com/input-lag/">of course an iPhone responds much more quickly</a>. Taps cause links to load fairly quickly and scrolling also works fine (it's a little jerky, but almost nothing is really smooth on this device). This seems like the kind of thing people are referring to when they say that you can get better performance if you ship a heavy payload, but there aren't many examples of that which actually improve performance on low-end devices.</p> | |||||
<h3 id="appendix-articles-on-web-performance-issues">Appendix: articles on web performance issues</h3> | |||||
<ul> | |||||
<li>2015: Maciej Cegłowski: <a href="https://idlewords.com/talks/website_obesity.htm">The Website Obesity Crisis</a> | |||||
<ul> | |||||
<li>Size: <code>1.0 MB</code> / <code>1.1 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.9s</code> / <code>1.4s</code> | |||||
<ul> | |||||
<li>Scrolling a bit jerky, images take a little bit of time to appear if scrolling very quickly (jumping halfway down page from top), but delay is below what almost any user would perceive when scrolling a normal distance.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2015: Nate Berkopec: <a href="https://www.speedshop.co/2015/11/05/page-weight-doesnt-matter.html">Page Weight Doesn't Matter</a> | |||||
<ul> | |||||
<li>Size: <code>80 kB</code> / <code>0.2 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.8s</code> / <code>0.7s</code> | |||||
<ul> | |||||
<li>Does lazy loading, page downloads <code>650 kB</code> / <code>1.8 MB</code> if you scroll through the entire page, but scrolling is only a little jerky and the lazy loading doesn't cause delays. Probably the only page I've tried that does lazy loading in a way that makes the experience better and not worse on a slow device; I didn't test on a slow connection, where this would still make the experience worse.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>1.1s</code> / <code>1s</code> | |||||
<ul> | |||||
<li>Scrolling basically unusable; scroll extremely jerky and moves a random distance, often takes over <code>1s</code> for text to render when scrolling to new text; can be much worse with images that are lazy loaded. Even though this is the best implementation of lazy loading I've seen in the wild, the <code>Itel P32</code> still can't handle it.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2017: Dan Luu: <a href="https://danluu.com/web-bloat/">How web bloat impacts users with slow connections</a> | |||||
<ul> | |||||
<li>Size: <code>14 kB</code> / <code>57 kB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.5s</code> / <code>0.3s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>:<code>0.7s</code> / <code>0.5 s</code></li> | |||||
</ul></li> | |||||
<li>2017-2024+: Alex Russell: <a href="https://infrequently.org/series/performance-inequality/">The Performance Inequality Gap (series)</a> | |||||
<ul> | |||||
<li>Size: <code>82 kB</code> / <code>0.1 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.5s</code> / <code>0.4s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>0.7s</code> / <code>0.4s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2024: Nikita Prokopov (Tonsky): <a href="https://tonsky.me/blog/js-bloat/">JavaScript Bloat in 2024</a> | |||||
<ul> | |||||
<li>Size: <code>14 MB</code> / <code>14 MB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.8s</code> / <code>1.9s</code> | |||||
<ul> | |||||
<li>When scrolling, it takes a while for images to show up (500ms or so) and the scrolling isn't smooth, but it's not jerky enough that it's difficult to scroll to the right place.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>2.5s</code> / <code>3s</code> | |||||
<ul> | |||||
<li>Scrolling isn't smooth. Scrolling accurately is a bit difficult, but can generally scroll to where you want if very careful. Generally takes a bit more than <code>1s</code> for new content to appear when you scroll a significant distance.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>2024: Dan Luu: <a href="https://danluu.com/slow-device/">This post</a> | |||||
<ul> | |||||
<li>Size: <code>25 kB</code> / <code>74 kB</code></li> | |||||
<li><code>Tecno Spark 8C</code>: <code>0.6s</code> / <code>0.5s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine.</li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code>: <code>1.3s</code> / <code>1.1s</code> | |||||
<ul> | |||||
<li>Scrolling and interaction work fine, although I had to make a change for this to be the case — this doc originally had an embedded video, which the <code>Itel P32</code> couldn't really handle. | |||||
<ul> | |||||
<li>Note that, while these numbers are worse than the numbers for "Page Weight Doesn't Matter", this page is usable after load, which that other page isn't beacuse it execute some kind of lazy loading that's too complex for this phone to handle in a reasonable timeframe. | |||||
<br></li> | |||||
</ul></li> | |||||
</ul></li> | |||||
</ul></li> | |||||
</ul> | |||||
<h3 id="appendix-empathy-for-non-rich-users">Appendix: empathy for non-rich users</h3> | |||||
<p>Something I've observed over time, as programming has become more prestigious and more lucrative, is <a href="https://mastodon.social/@danluu/109901711437753852">that people have tended to come from wealthier backgrounds</a> and have less exposure to people with different income levels. An example we've discussed before, is at a well-known, prestigious, startup that has a very left-leaning employee base, where everyone got rich, on a discussion about the covid stimulus checks, in a slack discussion, a well meaning progressive employee said that it was pointless because people would just use their stimulus checks to buy stock. This person had, apparently, never talked to any middle-class (let alone poor) person about where their money goes or looked at the data on who owns equity. And that's just looking at American wealth. When we look at world-wide wealth, the general level of understanding is much lower. People seem to really underestimate the dynamic range in wealth and income across the world. From having talked to quite a few people about this, a lot of people seem to have mental buckets for "poor by American standards" (buys stock with stimulus checks) and "poor by worldwide standards" (maybe doesn't even buy stock), but the range of poverty in the world dwarfs the range of poverty in America to an extent that not many wealthy programmers seem to realize.</p> | |||||
<p>Just for example, <a href="https://mastodon.social/@danluu/109537302116865694">in this discussion how lucky I was (in terms of financial opportunities) that my parents made it to America</a>, someone mentioned that it's not that big a deal because they had great financial opportunities in Poland. For one thing, with respect to the topic of the discussion, the probability that someone will end up with a high-paying programming job (senior staff eng at a high-paying tech company) or equivalent, I suspect that, when I was born, being born poor in the U.S. gives you better odds than being fairly well off in Poland, but I could believe the other case as well if presented with data. But if we're comparing Poland v. U.S. to Vietnam v. U.S., if I spend <abbr title="so, these probably aren't the optimal numbers one would use for a comparison, but I think they're good enough for this purpose">15 seconds looking up rough wealth numbers for these countries</abbr> in the year I was born, the GDP/capita ratio of U.S. : Poland was ~8:1, whereas it was ~50 : 1 for Poland : Vietnam. The difference in wealth between Poland and Vietnam was roughly the square of the difference between the U.S. and Poland, so Poland to Vietnam is roughly equivalent to Poland vs. some hypothetical country that's richer than the U.S. by the amount that the U.S. is richer than Poland. These aren't even remotely comparable, but a lot of people seem to have this mental model that there's "rich countries" and "not rich countries" and "not rich countries" are all roughly in the same bucket. GDP/capita isn't ideal, but it's easier to find than percentile income statistics; the quick search I did also turned up that annual income in Vietnam then was something like $200-$300 a year. Vietnam was also going through the tail end of a famine whose impacts are a bit difficult to determine because statistics here seem to be gamed, but if you believe the mortality rate statistics, the famine caused total overall mortality rate to jump to double the normal baseline.</p> | |||||
<p>Of course, at the time, the median person in a low-income country wouldn't have had a computer, let alone internet access. But, today it's fairly common for people in low-income countries to have devices. Many people either don't seem to realize this or don't understand what sorts of devices a lot of these folks use.</p> | |||||
<p>On the Discourse founder's comments on iOS vs. Android marketshare, Fabian notes</p> | |||||
<blockquote> | |||||
<p>In the US, according to the most recent data I could find (for 2023), iPhones have around 60% marketshare. In the EU, it's around 33%. | |||||
This has knock-on effects. Not only do iOS users skew towards the wealthier end, they also skew towards the US.</p> | |||||
<p>There's some secondary effects from this too. For example, in the US, iMessage is very popular for group chats etc. and infamous for interoperating very poorly with Android devices in a way that makes the experience for Android users very annoying (almost certainly intentionally so).</p> | |||||
<p>In the EU, not least because Android is so much more prominent, iMessage is way less popular and anecdotally, even iPhone users among my acquaintances who would probably use iMessage in the US tend to use WhatsApp instead.</p> | |||||
<p>Point being, globally speaking, recent iOS + fast Internet is even more skewed towards a particular demographic than many app devs in the US seem to be aware.</p> | |||||
</blockquote> | |||||
<p>And on the comment about mobile app vs. web app sizes, Fabian said:</p> | |||||
<blockquote> | |||||
<p>One more note from experience: apps you install when you install them, and generally have some opportunity to hold off on updates while you're on a slow or metered connection (or just don't have data at all).</p> | |||||
<p>Back when I originally got my US phone, I had no US credit history and thus had to use prepaid plans. I still do because it's fine for what I actually use my phone for most of the time, but it does mean that when I travel to Germany once a year, I don't get data roaming at all. (Also, phone calls in Germany cost me $1.50 apiece, even though T-Mobile is the biggest mobile provider in Germany - though, of course, not T-Mobile US.)</p> | |||||
<p>Point being, I do get access to free and fast Wi-Fi at T-Mobile hotspots (e.g. major train stations, airports etc.) and on inter-city trains that have them, but I effectively don't have any data plan when in Germany at all.</p> | |||||
<p>This is completely fine with mobile phone apps that work offline and sync their data when they have a connection. But web apps are unusable while I'm not near a public Wi-Fi.</p> | |||||
<p>Likewise I'm fine sending an email over a slow metered connection via the Gmail app, but I for sure wouldn't use any web-mail client that needs to download a few MBs worth of zipped JS to do anything on a metered connection.</p> | |||||
<p>At least with native app downloads, I can prepare in advance and download them while I'm somewhere with good internet!</p> | |||||
</blockquote> | |||||
<p>Another comment from Fabian (this time paraphrased since this was from a conversation), is that people will often justify being quantitatively hugely slower because there's a qualitative reason something should be slow. One example he gave was that screens often take a long time to sync their connection and this is justified because there are operations that have to be done that take time. For a long time, these operations would often take seconds. Recently, a lot of displays sync much more quickly because Nvidia specifies how long this can take for something to be "G-Sync" certified, so display makers actually do this in a reasonable amount of time now. While it's true that there are operations that have to be done that take time, there's no fundamental reason they should take as much time as they often used to. Another example he gave was on how someone was justifying how long it took to read thousands of files because the operation required a lot of syscalls and "syscalls are slow", which is a qualitatively true statement, but if you look at the actual cost of a syscall, in the case under discussion, the cost of a syscall was many orders of magnitude from being costly enough to be a reasonable explanation for why it took so long to read thousands of files.</p> | |||||
<p>On this topic, when people point out that a modern website is slow, someone will generally respond with the qualitative defense that the modern website has these great features, which the older website is lacking. And while it's true that (for example) Discourse has features that MyBB doesn't, it's hard to argue that its feature set justifies being <code>33x</code> slower.</p> | |||||
<h3 id="appendix-experimental-details">Appendix: experimental details</h3> | |||||
<p>With the exception of danluu.com and, arguably, HN, for each site, I tried to find the "most default" experience. For example, for WordPress, this meant a demo blog with the current default theme, twentytwentyfour. In some cases, this may not be the most likely thing someone uses today, e.g., for Shopify, I looked at the first thing that theme they give you when you browse their themes, but I didn't attempt to find theme data to see what the most commonly used theme is. For this post, I wanted to do all of the data collection and analysis as a short project, something that takes less than a day, so there were a number of shortcuts like this, which will be described below. I don't think it's wrong to use the first-presented Shopify theme in a decent fraction of users will probably use the first-presente theme, but that is, of course, less representative than grabbing whatever the most common theme is and then also testing many different sites that use that theme to see how real-world performance varies when people modify the theme for their own use. If I worked for Shopify or wanted to do competitive analysis on behalf of a competitor, I would do that, but for a one-day project on how large websites impact users on low-end devices, the performance of Shopify demonstrated here seems ok. I actually <a href="https://mastodon.social/@danluu/111994372051118539">did the initial work for this around when I ran these polls</a>, back in February; I just didn't have time to really write this stuff up for a month.</p> | |||||
<p>For the tests on laptops, I tried to have the laptop at ~60% battery, not plugged in, and the laptop was idle for enough time to return to thermal equilibrium in a room at 20°C, so pages shouldn't be impacted by prior page loads or other prior work that was happening on the machine.</p> | |||||
<p>For the mobile tests, the phones were at ~100% charge and plugged in, and also previously at 100% charge so the phones didn't have any heating effect you can get from rapidly charging. As noted above, these tests were formed with <code>1Gbps</code> WiFi. No other apps were running, the browser had no other tabs open, and the only apps that were installed on the device, so no additional background tasks should've been running other than whatever users are normally subject to by the device by default. A real user with the same device is going to see worse performance than we measured here in almost every circumstance except if running Chrome Dev Tools on a phone significantly degrades performance. I noticed that, on the Itel P32, scrolling was somewhat jerkier with Dev Tools running than when running normally but, since this was a one-day project, I didn't attempt to quantify this and if it impacts some sites much more than others. In absolute terms, the overhead can't be all that large because the fastest sites are still fairly fast with Dev Tools running, but if there's some kind of overhead that's super-linear in the amount of work the site does (possibly indirectly, if it causes some kind of resource exhaustion), then that could be a problem in measurements of some sites.</p> | |||||
<p>Sizes were all measured on mobile, so in cases where different assets are loaded on mobile vs. desktop, the we measured the mobile asset sizes. <code>CPU</code> was measured as CPU time on the main thread (I did also record time on other threads for sites that used other threads, but didn't use this number; if <code>CPU</code> were a metric people wanted to game, time on other threads would have to be accounted for to prevent sites from trying to offload as much work as possible to other threads, but this isn't currently an issue and time on main thread is more directly correlated to usability than sum of time across all threads, and the metric that would work for gaming is less legible with no upside for now).</p> | |||||
<p>For WiFi speeds, speed tests had the following numbers:</p> | |||||
<ul> | |||||
<li><code>M3 Max</code> | |||||
<ul> | |||||
<li>Netflix (fast.com) | |||||
<ul> | |||||
<li>Download: <code>850 Mbps</code></li> | |||||
<li>Upload: <code>840 Mbps</code></li> | |||||
<li>Latency (unloaded / loaded): <code>3ms</code> / <code>8ms</code></li> | |||||
</ul></li> | |||||
<li>Ookla | |||||
<ul> | |||||
<li>Download: <code>900 Mbps</code></li> | |||||
<li>Upload: <code>840 Mbps</code></li> | |||||
<li>Latency (unloaded / download / upload): <code>3ms</code> / <code>8ms</code> / <code>13ms</code></li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li><code>Tecno Spark 8C</code> | |||||
<ul> | |||||
<li>Netflix (fast.com) | |||||
<ul> | |||||
<li>Download: <code>390 Mbps</code></li> | |||||
<li>Upload: <code>210 Mbps</code></li> | |||||
<li>Latency (unloaded / loaded): <code>2ms</code> / <code>30ms</code></li> | |||||
</ul></li> | |||||
<li>Oookla | |||||
<ul> | |||||
<li>Ookla web app fails, can't see results</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li><code>Itel P32</code> | |||||
<ul> | |||||
<li>Netflix | |||||
<ul> | |||||
<li>Download: <code>44 Mbps</code></li> | |||||
<li>Upload: test fails to work (sends one chunk of data and then hangs, sending no more data)</li> | |||||
<li>Latency (unloaded / loaded): <code>4ms</code> / <code>400ms</code></li> | |||||
</ul></li> | |||||
<li>Okta | |||||
<ul> | |||||
<li>Download: <code>45 Mbps</code></li> | |||||
<li>Upload: test fails to work</li> | |||||
<li>Latency: test fails to display latency</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
</ul> | |||||
<p>One thing to note is that the <code>Itel P32</code> doesn't really have the ability to use the bandwidth that it nominally has. Looking at the top Google reviews, none of them mention this. <a href="https://www.nairaland.com/4628841/itel-p32-review-great-those" rel="nofollow">The first review reads</a></p> | |||||
<blockquote> | |||||
<p>Performance-wise, the phone doesn’t lag. It is powered by the latest Android 8.1 (GO Edition) ... we have 8GB+1GB ROM and RAM, to run on a power horse of 1.3GHz quad-core processor for easy multi-tasking ... I’m impressed with the features on the P32, especially because of the price. I would recommend it for those who are always on the move. And for those who take battery life in smartphones has their number one priority, then P32 is your best bet.</p> | |||||
</blockquote> | |||||
<p><a href="https://techjaja.com/itel-p32-review-dual-camera-smartphone-alarming-price-tag/" rel="nofollow">The second review reads</a></p> | |||||
<blockquote> | |||||
<p>Itel mobile is one of the leading Africa distributors ranking 3rd on a continental scale ... the light operating system acted up to our expectations with no sluggish performance on a 1GB RAM device ... fairly fast processing speeds ... the Itel P32 smartphone delivers the best performance beyond its capabilities ... at a whooping UGX 330,000 price tag, the Itel P32 is one of those amazing low-range like smartphones that deserve a mid-range flag for amazing features embedded in a single package.</p> | |||||
</blockquote> | |||||
<p><a href="https://pctechmag.com/2018/08/itel-p32-full-review-much-more-than-just-a-budget-entry-level-smartphone/" rel="nofollow">The third review reads</a></p> | |||||
<blockquote> | |||||
<p>"Much More Than Just a Budget Entry-Level Smartphone ... Our full review after 2 weeks of usage ... While switching between apps, and browsing through heavy web pages, the performance was optimal. There were few lags when multiple apps were running in the background, while playing games. However, the overall performance is average for maximum phone users, and is best for average users [screenshot of game] Even though the game was skipping some frames, and automatically dropped graphical details it was much faster if no other app was running on the phone.</p> | |||||
</blockquote> | |||||
<p>Notes on sites:</p> | |||||
<ul> | |||||
<li>Wix | |||||
<ul> | |||||
<li>www.wix.com/website-template/view/html/3173?originUrl=https%3A%2F%2Fwww.wix.com%2Fwebsite%2Ftemplates%2Fhtml%2Fmost-popular&tpClick=view_button&esi=a30e7086-28db-4e2e-ba22-9d1ecfbb1250: this was the first entry when I clicked to get a theme</li> | |||||
<li><code>LCP</code> was misleading on every device</li> | |||||
<li>On the <code>Tecno Spark 8C</code>, scrolling never really works. It's very jerky and this never settles down</li> | |||||
<li>On the <code>Itel P32</code>, the page fails non-deterministically (different errors on different loads); it can take quite a while to error out; it was <code>23s</code> on the first run, with the CPU pegged for <code>28s</code></li> | |||||
</ul></li> | |||||
<li>Patreon | |||||
<ul> | |||||
<li>www.patreon.com/danluu: used my profile where possible</li> | |||||
<li>Scrolling on Patreon and finding old posts is so painful that I maintain <a href="https://danluu.com/#pt">my own index of my Patreon posts</a> so that I can find my old posts without having to use Patreon. Although Patreon's numbers in the table don't look that bad in the table when you're on a fast laptop, that's just for the initial load. The performance as you scroll is bad enough that I don't think that, today, there exists a computer and internet connection that browse Patreon with decent performance.</li> | |||||
</ul></li> | |||||
<li>Threads | |||||
<ul> | |||||
<li>threads.net/danluu.danluu: used my profile where possible</li> | |||||
<li>On the <code>Itel P32</code>, this technically doesn't load correctly and could be marked as <code>FAIL</code>, but it's close enough that I counted it. The thing that's incorrect is that profile photos have a square box around then | |||||
<ul> | |||||
<li>However, as with the other heavy pages, interacting with the page doesn't really work and the page is unusable, but this appears to be for the standard performance reasons and not because the page failed to render</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>Twitter | |||||
<ul> | |||||
<li>twitter.com/danluu: used my profile where possible</li> | |||||
</ul></li> | |||||
<li>Discourse | |||||
<ul> | |||||
<li>meta.discourse.org: this is what turned up when I searched for an official forum.</li> | |||||
<li>As discussed above, the <code>LCP</code> is highly gamed and basically meaningless. We linked to a post where the Discourse folks note that, on slow loads, they put a giant splash screen up at <code>2s</code> to cap the <code>LCP</code> at <code>2s</code>. Also notable is that, on loads that are faster than the 2s, the <code>LCP</code> is also highly gamed. For example, on the <code>M3 Max</code> with low-latency <code>1Gbps</code> internet, the <code>LCP</code> was reported as <code>115ms</code>, but the page loads actual content at <code>1.1s</code>. This appears to use the same fundamental trick as "Discourse Splash", in that it paints a huge change onto the screen and then carefully loads smaller elements to avoid having the actual page content detected as the <code>LCP</code>.</li> | |||||
<li>On the <code>Tecno Spark 8C</code>, scrolling is unpredictable and can jump too far, triggering loading from infinite scroll, which hangs the page for <code>3s-10s</code>. Also, the entire browser sometimes crashes if you just let the browser sit on this page for a while.</li> | |||||
<li>On the <code>Itel P32</code>, an error message is displayed after <code>7.5s</code></li> | |||||
</ul></li> | |||||
<li>Bluesky | |||||
<ul> | |||||
<li>bsky.app/profile/danluu.com</li> | |||||
<li>Displays a blank screen on the <code>Itel P32</code></li> | |||||
</ul></li> | |||||
<li>Squarespace | |||||
<ul> | |||||
<li>cedar-fluid-demo.squarespace.com: this was the second theme that showed up when I clicked themes to get a theme; the first was one called "Bogart", but that was basically a "coming soon" single page screen with no content, so I used the second theme instead of the first one.</li> | |||||
<li>A lot of errors and warnings in the console with the <code>Itel P32</code>, but the page appears to load and work, although interacting with it is fairly slow and painful</li> | |||||
<li><code>LCP</code> on the <code>Tecno Spark 8C</code> was significantly before the page content actually loaded</li> | |||||
</ul></li> | |||||
<li>Tumblr | |||||
<ul> | |||||
<li>www.tumblr.com/slatestarscratchpad: used this because I know this tumblr exists. I don't read a lot of tumblers (maybe three or four), and this one seemed like the closest thing to my blog that I know of on tumblr.</li> | |||||
<li>This page fails on the <code>Itel P32</code>, but doesn't <code>FAIL</code>. The console shows that the JavaScript errors out, but the page still works fine (I tried scrolling, clicking links, etc., and these all worked), so you can actually go to the post you want and read it. The JS error appears to have made this page load much more quickly than it other would have and also made interacting with the page after it loaded fairly zippy.</li> | |||||
</ul></li> | |||||
<li>Shopify | |||||
<ul> | |||||
<li>themes.shopify.com/themes/motion/styles/classic/preview?surface_detail=listing&surface_inter_position=1&surface_intra_position=1&surface_type=all: this was the first theme that showed up when I looked for themes</li> | |||||
<li>On the first <code>M3/10</code> run, Chrome dev tools reported a nonsensical <code>697s</code> of CPU time (the run completed in a normal amount of time, well under <code>697s</code> or even <code>697/10s</code>. This run was ignored when computing results.</li> | |||||
<li>On the <code>Itel P32</code>, the page load never completes and it just shows a flashing cursor-like image, which is deliberately loaded by the theme. On devices that load properly, the flashing cursor image is immediately covered up by another image, but that never happens here.</li> | |||||
<li>I wondered if it wasn't fair to use this example theme because there's some stuff on the page that lets you switch theme styles, so I checked out actual uses of the theme (the page that advertises the theme lists users of the theme). I tried the first two listed real examples and they were both much slower than this demo page.</li> | |||||
</ul></li> | |||||
<li>Reddit | |||||
<ul> | |||||
<li>reddit.com</li> | |||||
<li>Has an unusually low <code>LCP*</code> compared to how long it takes for the page to become usable. Although not measured in this test, I generally find the page slow and sort of unusable on Intel Macbooks which are, by historical standards, extremely fast computers (unless I use old.reddit.com)</li> | |||||
</ul></li> | |||||
<li>Mastodon | |||||
<ul> | |||||
<li>mastodon.social/@danluu: used my profile where possible</li> | |||||
<li>Fails to load on <code>Itel P32</code>, just gives you a blank screen. Due to how long things generally take on the <code>Itel P32</code>, it's not obvious for a while if the page is failing or if it's just slow</li> | |||||
</ul></li> | |||||
<li>Quora | |||||
<ul> | |||||
<li>www.quora.com/Ever-felt-like-giving-up-on-your-dreams-How-did-you-come-out-of-it: I tried googling for quora + the username of a metafilter user who I've heard is now prolific on Quora. Rather than giving their profile page, Google returned this page, which appears to have nothing to do with the user I searched for. So, this isn't comparable to the social media profiles, but getting a random irrelevant Quora result from Google is how I tend to interact with Quora, so I guess this is representative of my Quora usage.</li> | |||||
<li>On the <code>Itel P32</code>, the page stops executing scripts at some point and doesn't fully load. This causes it to fail to display properly. Interacting with the page doesn't really work either.</li> | |||||
</ul></li> | |||||
<li>Substack | |||||
<ul> | |||||
<li>Used thezvi.substack.com because I know Zvi has a substack and writes about similar topics.</li> | |||||
</ul></li> | |||||
<li>vBulletin: | |||||
<ul> | |||||
<li>forum.vbulletin.com: this is what turned up when I searched for an official forum.</li> | |||||
</ul></li> | |||||
<li>Medium | |||||
<ul> | |||||
<li>medium.com/swlh: I don't read anything on Medium, so I googled for programming blogs on Medium and this was the top hit. From looking at the theme, it doesn't appear to be unusually heavy or particularly customized for a Medium blog. Since it appears to be widely read and popular, it's more likely to be served from a CDN and than some of the other blogs here.</li> | |||||
<li>On a run that wasn't a benchmark reference run, on the <code>Itel P32</code>, I tried scrolling starting 35s after loading the page. The delay to scroll was <code>5s-8s</code> and scrolling moved an unpredictable amount, making the page completely unusable. This wasn't marked as a <code>FAIL</code> in the table, but one could argue that this should be a <code>FAIL</code> since the page is unusable.</li> | |||||
</ul></li> | |||||
<li>Ghost | |||||
<ul> | |||||
<li>source.ghost.io because this is the current default Ghost theme and it was the first example I found</li> | |||||
</ul></li> | |||||
<li>Wordpress | |||||
<ul> | |||||
<li>2024.wordpress.net because this is the current default wordpress theme and this was the first example of it I found</li> | |||||
</ul></li> | |||||
<li>XenForo | |||||
<ul> | |||||
<li>xenforo.com/community/: this is what turned up when I searched for an official forum</li> | |||||
<li>On the <code>Itel P32</code>, the layout is badly wrong and page content overlaps itself. There's no reasonable way to interact with the element you want because of this, and reading the text requires reading text that's been overprinted multiple times.</li> | |||||
</ul></li> | |||||
<li>Wordpress (old) | |||||
<ul> | |||||
<li>Used thezvi.wordpress.com because it has the same content as Zvi's substack, and happens to be on some old wordpress theme that used to be a very common choice</li> | |||||
</ul></li> | |||||
<li>phpBB | |||||
<ul> | |||||
<li>www.phpbb.com/community/index.php: this is what turned up when I searched for an official forum.</li> | |||||
</ul></li> | |||||
<li>MyBB | |||||
<ul> | |||||
<li>community.mybb.com: this is what turned up when I searched for an official forum.</li> | |||||
<li>Site doesn't serve up a mobile version. In general, I find the desktop version of sites to be significantly better than the mobile version when on a slow device, so this works quite well, although they're likely penalized by Google for this.</li> | |||||
</ul></li> | |||||
<li>HN | |||||
<ul> | |||||
<li>news.ycombinator.com</li> | |||||
<li>In principle, HN should be the slowest social media site or link aggregator because it's written in a custom Lisp that isn't highly optimized and the code was originally written with brevity and cleverness in mind, which generally gives you fairly poor performance. However, that's only poor relative to what you'd get if you were writing high-performance code, which is not a relevant point of comparison here.</li> | |||||
</ul></li> | |||||
<li>danluu.com | |||||
<ul> | |||||
<li>Self explanatory</li> | |||||
<li>This currently uses a bit less CPU than HN, but I expect this to eventually use more CPU as the main page keeps growing. At the moment, this page has 176 links to 168 articles vs. HN's 199 links to 30 articles but, barring an untimely demise, this page should eventually have more links than HN. | |||||
<ul> | |||||
<li>As noted above, I find that pagination for such small pages makes the browsing experience much worse on slow devices or with bad connections, so I don't want to "optimize" this by paginating it or, even worse, doing some kind of dynamic content loading on scroll.</li> | |||||
</ul></li> | |||||
</ul></li> | |||||
<li>Woo Commerce | |||||
<ul> | |||||
<li>I originally measured Woo Commerce as well but, unlike the pages and platforms tested above, I didn't find that being fast or slow on the initial load was necessarily representative of subsequent performance of other action, so this wasn't included in the table because having this in the table is sort of asking for a comparison against Shopify. In particular, while the "most default" Woo theme I could find was significantly faster than the "most default" Shopify theme on initial load on a slow device, performance was multidimensional enough that it was easy to find realistic scenarios where Shopify was faster than Woo and vice versa on a slow device, which is quite different from what I saw with newer blogging platforms like Substack and Medium compared to older platforms like Wordpress, or a modern forum like Discourse versus the older PHP-based forums. A real comparison of shopping sites that have carts, checkout flows, etc., would require a better understanding of real-world usage of these sites than I was going to get in a single day.</li> | |||||
</ul></li> | |||||
<li>NodeBB | |||||
<ul> | |||||
<li>community.nodebb.org</li> | |||||
<li>This wasn't in my original tests and I only tried this out because one of the founders of NodeBB suggested it, saying "I am interested in seeing whether @nodebb@fosstodon.org would fare better in your testing. We spent quite a bit of time over the years on making it wicked fast, and I personally feel it is a better representation of modern forum software than Discourse, at least on speed and initial payload."</li> | |||||
<li>I didn't do the full set of tests because I don't keep the <code>Itel P32</code> charged (the battery is in rough shape and discharges quite quickly once unplugged, so I'd have to wait quite a while to get it into a charged state)</li> | |||||
<li>On the tests I did, it got <code>0.3s/0.4s</code> on the <code>M1</code> and <code>3.4s/7.2s</code> on the <code>Tecno Spark 8C</code>. This is moderately slower than vBulletin and significantly slower than the faster php forums, but much faster than Discourse. If you need a "modern" forum for some reason and want to have your forum be usable by people who aren't, by global standards, rich, this seems like it could work.</li> | |||||
<li>Another notable thing, given that it's a "modern" site, is that interaction works fine after initial load; you can scroll and tap on things and this all basically works, nothing crashed, etc.</li> | |||||
<li>Sizes were <code>0.9 MB</code> / <code>2.2 MB</code>, so also fairly light for a "modern" site and possibly usable on a slow connection, although slow connections weren't tested here.</li> | |||||
</ul></li> | |||||
</ul> | |||||
<p>Another kind of testing would be to try to configure pages to look as similar as possible. I'd be interested in seeing that results for that if anyone does it, but that test would be much more time consuming. For one thing, it requires customizing each site. And for another, it requires deciding what sites should look like. If you test something danluu.com-like, every platform that lets you serve up something light straight out of a CDN, like Wordpress and Ghost, should score similarly, with the score being dependent on the CDN and the CDN cache hit rate. Sites like Medium and Substack, which have relatively little customizability would score pretty much as they do here. Realistically, from looking at what sites exist, most users will create sites that are slower than the "most default" themes for Wordpress and Ghost, although it's plausible that readers of this blog would, on average, do the opposite, so you'd probably want to test a variety of different site styles.</p> | |||||
<h3 id="appendix-this-site-vs-sites-that-don-t-work-on-slow-devices-or-slow-connections">Appendix: this site vs. sites that don't work on slow devices or slow connections</h3> | |||||
<p>Just as an aside, something I've found funny for a long time is that I get quite a bit of hate mail about the styling on this page (and a similar volume of appreciation mail). By hate mail, I don't mean polite suggestions to change things, I mean the equivalent of road rage, but for web browsing; web rage. I know people who run sites that are complex enough that they're unusable by a significant fraction of people in the world. How come people are so incensed about the styling of this site and, proportionally, basically don't care at all that the web is unusable for so many people?</p> | |||||
<p>Another funny thing here is that the people who appreciate the styling generally appreciate that the site doesn't override any kind of default styling, letting you make the width exactly what you want (by setting your window size how you want it) and it also doesn't override any kind of default styling you apply to sites. The people who are really insistent about this want everyone to have some width limit they prefer, some font they prefer, etc., but it's always framed in a way as if they don't want it, it's really for the benefit of people at large even though accommodating the preferences of the web ragers would directly oppose the preferences of people who prefer (just for example) to be able to adjust the text width by adjusting their window width.</p> | |||||
<p>Until I pointed this out tens of times, this iteration would usually start with web ragers telling me that "studies show" that narrower text width is objectively better, but on reading every study that exists on the topic that I could find, I didn't find this to be the case. Moreover, on asking for citations, it's clear that people saying this generally hadn't read any studies on this at all and would sometimes hastily send me a study that they did not seem to have read. When I'd point this out, people would then change their argument to how studies can't really describe the issue (odd that they'd cite studies in the first place), although one person cited a book to me (which I read and they, apparently, had not since it also didn't support their argument) and then move to how this is what everyone wants, even though that's clearly not the case, both from the comments I've gotten as well as the data I have from when I made the change.</p> | |||||
<p>Web ragers who have this line of reasoning generally can't seem to absorb the information that their preferences are not universal and will insist that they regardless of what people say they like, which I find fairly interesting. On the data, when I switched from Octopress styling (at the time, the most popular styling for programming bloggers) to the current styling, I got what appeared to be a causal increase in traffic and engagement, so it appears that not only do people who write me appreciation mail about the styling like the styling, the overall feeling of people who don't write to me appears to be that the site is fine and apparently more appealing than standard programmer blog styling. When I've noted this, people tend to become become further invested in the idea that their preferences are universal and that people who think they have other preferences are wrong and reply with total nonsense.</p> | |||||
<p>For me, two questions I'm curious about are why do people feel the need to fabricate evidence on this topic (referring to studies when they haven't read any, googling for studies and then linking to one that says the opposite of what they claim it says, presumably because they didn't really read it, etc.) in order to claim that there are "objective" reasons their preferences are universal or correct, and why are people so much more incensed by this than by the global accessibility problems caused by typical web design? On the latter, I suspect if you polled people with an abstract survey, they would rate global accessibility to be a larger problem, but by revealed preference both in terms of what people create as well as what irritates them enough to send hate mail, we can see that having fully-adjustable line width and not capping line width at their preferred length is important to do something about whereas global accessibility is not. As noted above, people who run sites that aren't accessible due to performance problems generally get little to no hate mail about this. And when I use a default Octopress install, I got zero hate mail about this. Fewer people read my site at the time, but my traffic volume hasn't increased by a huge amount since then and the amount of hate mail I get about my site design has gone from zero to a fair amount, <abbr title="To find the plausible range of underlying ratios, we can do a simple Bayesian adjustment here and we still find that the ratio of hate mail has increased by much more than the increase in traffic; maybe one can argue that hate mail for slow sites is spread across all slow sites, so a second adjustment needs to be done here?">an infinitely higher ratio</abbr> than the increase in traffic.</p> | |||||
<p>To be clear, I certainly wouldn't claim that the design on this site is optimal. <a href="https://danluu.com/octopress-speedup/">I just removed the CSS from the most popular blogging platform for programmers at the time because that CSS seemed objetively bad for people with low-end connections</a> and, as a side effect, got more traffic and engagement overall, not just from locations where people tend to have lower end connections and devices. No doubt a designer who cares about users on low-end connections and devices could do better, but there's something quite odd about both the untruthfulness and the vitriol of comments on this.</p> |
<!doctype html><!-- This is a valid HTML5 document. --> | |||||
<!-- Screen readers, SEO, extensions and so on. --> | |||||
<html lang="fr"> | |||||
<!-- Has to be within the first 1024 bytes, hence before the `title` element | |||||
See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset --> | |||||
<meta charset="utf-8"> | |||||
<!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 --> | |||||
<!-- The viewport meta is quite crowded and we are responsible for that. | |||||
See: https://codepen.io/tigt/post/meta-viewport-for-2015 --> | |||||
<meta name="viewport" content="width=device-width,initial-scale=1"> | |||||
<!-- Required to make a valid HTML5 document. --> | |||||
<title>Écrire ou coder l’éditorial (archive) — David Larlet</title> | |||||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||||
<!-- That good ol' feed, subscribe :). --> | |||||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||||
<!-- Is that even respected? Retrospectively? What a shAItshow… | |||||
https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ --> | |||||
<meta name="robots" content="noai, noimageai"> | |||||
<!-- Documented, feel free to shoot an email. --> | |||||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||||
<script> | |||||
function toggleTheme(themeName) { | |||||
document.documentElement.classList.toggle( | |||||
'forced-dark', | |||||
themeName === 'dark' | |||||
) | |||||
document.documentElement.classList.toggle( | |||||
'forced-light', | |||||
themeName === 'light' | |||||
) | |||||
} | |||||
const selectedTheme = localStorage.getItem('theme') | |||||
if (selectedTheme !== 'undefined') { | |||||
toggleTheme(selectedTheme) | |||||
} | |||||
</script> | |||||
<meta name="robots" content="noindex, nofollow"> | |||||
<meta content="origin-when-cross-origin" name="referrer"> | |||||
<!-- Canonical URL for SEO purposes --> | |||||
<link rel="canonical" href="https://www.lobrassard.net/carnet/2024-03-22-ecrire-coder-editorial.html"> | |||||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||||
<article> | |||||
<header> | |||||
<h1>Écrire ou coder l’éditorial</h1> | |||||
</header> | |||||
<nav> | |||||
<p class="center"> | |||||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use> | |||||
</svg> Accueil</a> • | |||||
<a href="https://www.lobrassard.net/carnet/2024-03-22-ecrire-coder-editorial.html" title="Lien vers le contenu original">Source originale</a> | |||||
<br> | |||||
Mis en cache le 2024-03-25 | |||||
</p> | |||||
</nav> | |||||
<hr> | |||||
<p>À la <a href="https://www.ecrituresnumeriques.ca/" target="_blank" rel="external">Chaire de recherche du Canada sur les écritures numériques</a>, on travaille (notamment) sur la revue <a href="https://www.sens-public.org/"><cite>Sens public</cite></a>. Le défi du jour consistait à adapter la présentation d’extraits de poésie en grec ancien selon le contexte, ainsi qu’une numérotation des lignes sous forme d’annotation. Je ne connais rien au grec ancien ni à la poésie qui s’y rattache (et je me doute que c’est le cas pour bien des gens !), mais il me semble que la démarche éditoriale mise en pratique ici (qui consiste en un dialogue interdisciplinaire) mérite qu’on s’y intéresse un tout petit moment.</p> | |||||
<h2 id="lédition-systématique-des-distiques-élégiaques">L’édition (systématique) des distiques élégiaques</h2> | |||||
<p>Examinons un <em>distique élégiaque</em>, dont voici une source au format texte brut :</p> | |||||
<div class="sourceCode" id="cb1"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>Νῦν μοι "Χαῖρε" λέγεις, ὅτε σου τὸ πρόσωπον ἀπῆλθεν </span> | |||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> κεῖνο τὸ τῆς λύγδου, βάσκανε, λειότερον· </span> | |||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>νῦν μοι προσπαίζεις, ὅτε τὰς τρίχας ἠφάνικάς σου </span> | |||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> τὰς ἐπὶ τοῖς σοβαροῖς αὐχέσι πλαζομένας.</span></code></pre></div> | |||||
<p>Le premier vers est en hexamètre, le deuxième est en pentamètre ; le troisième vers est en hexamètre, le quatrième en pentamètre ; etc. Il y a un patron : les <strong>vers impairs</strong> sont en <strong>hexamètre</strong> ; les <strong>vers pairs</strong> sont en <strong>pentamètre</strong> et devront de surcroît apparaître <strong>en retrait</strong>.</p> | |||||
<p>Au niveau du code, j’ai pensé systématiser la présentation visuelle pour toujours mettre en retrait les lignes paires d’un poème balisé comme tel. Seul hic : il est possible que les éditrices choisissent de tronquer un poème <strong>pour n’en montrer qu’un extrait</strong>, si bien qu’un style automatisant la mise en forme des lignes selon une règle de type pair/impair ne reflètera pas nécessairement la réalité. Il fallait donc que les éditrices puissent avoir la main sur la présentation des poèmes. <em>Faudrait-il leur donner la possibilité de mettre en retrait n’importe quel vers?</em></p> | |||||
<h2 id="wysiwym-à-la-rescousse">WYSIWYM à la rescousse</h2> | |||||
<p>La chaîne éditoriale de <cite>Sens public</cite> repose sur un processus de révision scientifique, mais aussi <strong>technique</strong>, et celui-ci commence à être éprouvé depuis de nombreuses années. Ce processus a pris la forme d’un projet de recherche : <a href="https://stylo.huma-num.fr/">Stylo</a>, un éditeur de texte pensé pour les sciences humaines.</p> | |||||
<p>La plateforme propose de saisir un corps de texte au format <strong>Markdown</strong>. Celui-ci sera traité avec le logiciel <a href="https://pandoc.org/"><code>pandoc</code></a>, créé par le philosophe John MacFarlane. Cela est bon à savoir, car le corps du texte pourra utiliser une syntaxe à la « saveur » de Pandoc. C’est précisément cette saveur qui nous permettra de baliser le texte avec des éléments qui seront pour nous <em>sémantiques</em>, suivant le paradigme du <em lang="en">what you see is what you mean</em> (<abbr title="What You See Is What You Mean" lang="en">WYSIWYM</abbr>) : écrivez, balisez explicitement ce que vous voulez dire.</p> | |||||
<div class="margin-note"> | |||||
<aside> | |||||
<a href="https://pandoc.org/" target="_blank" rel="external">Pandoc</a> est un parseur de documents. C’est un logiciel utilisé en ligne de commande qui permet d’effectuer des conversions entre plusieurs formats de documents (<code>docx</code>, <code>LaTeX</code>, <code>markdown</code>, <code>txt</code>, etc.). | |||||
</aside> | |||||
</div> | |||||
<p>La mise en retrait des vers en pentamètre a une <strong>valeur sémantique</strong> : elle est contextuellement, sémantiquement liée à un type de vers bien spécifique (le <em>pentamètre</em>). Sauf qu’en édition, et éventuellement dans le contexte des <a href="https://gren.openum.ca/" rel="external"><em>éditions critiques</em></a>, mieux vaut prendre le problème à l’envers de « l’intuition visuelle » : ce n’est pas parce qu’un vers est indenté qu’il devient un pentamètre, <strong>mais c’est bien parce que c’est un pentamètre qu’il devra apparaître en retrait</strong>.</p> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-exemple.jpg" alt="Exemple de vers d’un distique élégiaque dans l’édition courante. Les lignes paires sont mises en retrait."> | |||||
<figcaption aria-hidden="true">Exemple de vers d’un distique élégiaque dans l’édition courante. Les lignes paires sont mises en retrait.</figcaption> | |||||
</figure> | |||||
<p>Plutôt que de permettre aux éditrices d’apporter une correction visuelle arbitraire et <em>ad hoc</em>, nous avons opté pour une forme de <strong>balisage sémantique</strong>. Ce qui a semblé naturel, c’était de marquer à l’aide d’un balisage simple et explicite ; en somme, de les <em>nommer</em>. Ainsi, en utilisant la fonctionnalité <a href="https://pandoc.org/MANUAL.html#extension-bracketed_spans"><code>bracked_spans</code></a> de Pandoc pour le Markdown (balisage de type <code>[texte]{.classe}</code>), nous pouvons marquer chaque ligne <em>explicitement</em> comme étant un hexamètre ou un pentamètre.</p> | |||||
<div class="sourceCode" id="cb2"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">Lorem ipsum dolor sit amet si</span><span class="co">]</span>{.hexametre} </span> | |||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="co">[</span><span class="ot">Vade ma vaecum</span><span class="co">]</span>{.pentametre} </span> | |||||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">Lorem ipsum dolor sit amet si</span><span class="co">]</span>{.hexametre} </span> | |||||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="co">[</span><span class="ot">Vade ma vaecum</span><span class="co">]</span>{.pentametre} </span> | |||||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span> | |||||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="co"><!-- </span></span> | |||||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="co"> Note: la «mise en retrait» effectuée dans le texte</span></span> | |||||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="co"> source au format Markdown n'a aucun impact dans le rendu</span></span> | |||||
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="co"> final, et n'est utilisé qu'à titre de démonstration.</span></span> | |||||
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="co">--></span></span></code></pre></div> | |||||
<p>Grâce à cette syntaxe, nous avons indiqué un surcroît d’information <em>sans pour l’instant affecter le rendu visuel</em>. On rend ainsi possible la systématisation d’une (éventuelle) règle d’affichage en fonction du <strong>sens</strong>, de la <strong>sémantique du balisage</strong>.</p> | |||||
<h2 id="une-ligne-de-css-mais-non-la-moindre">Une ligne de CSS (mais non la moindre)</h2> | |||||
<p>La syntaxe utilisée pour baliser les vers n’a pas été choisie au hasard : elle permet d’indiquer une « <strong>classe</strong> » sur un passage du texte. Une classe fonctionne un peu comme un système d’étiquettes ou de catégories : chaque élément <em>balisé</em> du document peut en avoir une seule, ou plusieurs.</p> | |||||
<div class="margin-note"> | |||||
<aside> | |||||
Il est possible de combiner les classes pour en avoir plus d’une sur un même passage. Dans notre cas d’étude, nous pourrions par exemple marquer chaque vers en pentamètre de deux classes : une classe <code>.vers</code> et une classe <code>.vers--pentametre</code>, ce qui donnerait le balisage <code>[vers du poème]{.vers.vers--pentametre}</code>. | |||||
</aside> | |||||
</div> | |||||
<p>Une classe permet d’appliquer un <strong>style</strong> de manière systématique pour tous les éléments qui ont cette classe. Pour les documents web, la <strong>présentation visuelle</strong> est décrite séparément de la <strong>structure du document</strong>, grâce aux feuilles de style au langage <abbr lang="en" title="Cascading Style Sheets">CSS</abbr>.</p> | |||||
<p>Pour régler l’affichage des pentamètres en retrait, voici le morceau de solution que je propose (les quelques lignes de code fonctionnel sont précédées de plusieurs lignes de prose qui éclairent le contexte) :</p> | |||||
<div class="sourceCode" id="cb3"><pre class="sourceCode css"><code class="sourceCode css"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span> | |||||
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * Dans l'édition des distiques élégiaques, une ligne</span></span> | |||||
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * en pentamètre est visuellement mise en retrait.</span></span> | |||||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * Nous y appliquons une «marge» intérieure de `2em`</span></span> | |||||
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co"> * (`1em` correspond à la largeur d'un cadratin).</span></span> | |||||
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co"> * On utilise la propriété `margin-inline-start` pour</span></span> | |||||
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * appliquer le retrait en fonction de la direction</span></span> | |||||
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co"> * naturelle du texte dans son contexte linguistique</span></span> | |||||
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="co"> * (de gauche à droite en français, mais cela pourrait</span></span> | |||||
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="co"> * être de droite à gauche en yiddish).</span></span> | |||||
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span> | |||||
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="fu">.pentametre</span> {</span> | |||||
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">margin-inline-start</span>: <span class="dv">2</span><span class="dt">em</span><span class="op">;</span></span> | |||||
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div> | |||||
<p>Et voilà !</p> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-style-retrait.jpg" alt="Résultat visuel d’un poème dont on a balisé les lignes en pentamètre (avec mise en retrait uniforme)."> | |||||
<figcaption aria-hidden="true">Résultat visuel d’un poème dont on a balisé les lignes en pentamètre (avec mise en retrait uniforme).</figcaption> | |||||
</figure> | |||||
<h2 id="un-autre-défi-éditorial-la-numérotation-des-lignes">Un autre défi éditorial : la numérotation des lignes</h2> | |||||
<p>Les éditrices souhaitaient afficher le numéro de certaines lignes, comme cela se fait habituellement dans l’édition de ce type de texte (le <em>distique élégiaque</em>). La numérotation se fait aux lignes par bonds de cinq :</p> | |||||
<div class="sourceCode" id="cb4"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>Νῦν μοι "Χαῖρε" λέγεις, ὅτε σου τὸ πρόσωπον ἀπῆλθεν </span> | |||||
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> κεῖνο τὸ τῆς λύγδου, βάσκανε, λειότερον· </span> | |||||
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>νῦν μοι προσπαίζεις, ὅτε τὰς τρίχας ἠφάνικάς σου </span> | |||||
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> τὰς ἐπὶ τοῖς σοβαροῖς αὐχέσι πλαζομένας. </span> | |||||
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>μηκέτι μοι, μετέωρε, προσέρχεο μηδὲ συνάντα 5 <span class="co"><!-- Numéro --></span> </span> | |||||
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> ἀντὶ ῥόδου γὰρ ἐγὼ τὴν βάτον οὐ δέχομαι. </span></code></pre></div> | |||||
<div class="margin-note"> | |||||
<details open> | |||||
<summary> | |||||
Anti-exemple de numérotation | |||||
</summary> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-mauvaise-numerotation.jpg" alt="Exemple d’une mise en forme des numéros de ligne qu’on voudrait éviter : les numéros ne sont pas alignés (et ont sans doute été insérés à même le texte à citer). Anti-exemple envoyé par Mathilde V."> | |||||
<figcaption aria-hidden="true">Exemple d’une mise en forme des numéros de ligne qu’on voudrait éviter : les numéros ne sont pas alignés (et ont sans doute été insérés à même le texte à citer). Anti-exemple envoyé par Mathilde V.</figcaption> | |||||
</figure> | |||||
</details> | |||||
</div> | |||||
<p>Si les éditrices ajoutent le numéro de vers directement dans le texte, celui-ci apparaîtra au bout de la ligne, <em>comme s’il faisait partie du poème</em>. De plus, les vers n’ont pas la même largeur (ils n’ont pas la même métrique et le nombre de caractères est rarement le même) : les nombres ne seront pas alignés, ce qui n’est ni pratique ni visuellement harmonieux.</p> | |||||
<p>Solution ? Marquer l’intention de numérotation avec une balise « invisible » : un attribut <code>data-numerotation=""</code> inscrit sur la ligne que l’on souhaite numéroter. La numérotation des lignes se fait « manuellement », puisque ce sont souvent des extraits qui sont présentés et qui peuvent être tronqués.</p> | |||||
<div class="sourceCode" id="cb5"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"><!-- </span></span> | |||||
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="co"> La numérotation (qui prendra une mise en forme</span></span> | |||||
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"> particulière) sera appliquée sur la ligne qui est</span></span> | |||||
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co"> marquée de l'attribut `data-numerotation`.</span></span> | |||||
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co"> --></span></span> | |||||
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span> | |||||
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">μηκέτι μοι, μετέωρε, προσέρχεο μηδὲ συνάντα</span><span class="co">]</span>{data-numerotation="15"} </span> | |||||
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> ἀντὶ ῥόδου γὰρ ἐγὼ τὴν βάτον οὐ δέχομαι. </span></code></pre></div> | |||||
<p>La <strong>mise en forme</strong>, traitée séparément de la structure logique du document, se fait aisément avec quelques propriétés <abbr lang="en" title="Cascading Style Sheets">CSS</abbr> :</p> | |||||
<ol type="1"> | |||||
<li>afficher le numéro de la ligne depuis un attribut (une « métadonnée ») ;</li> | |||||
<li>conserver un espace minimal pour que le texte ne vienne jamais se coller au numéro ;</li> | |||||
<li>envoyer le numéro <em>à la fin de la ligne</em>, peu importe si nous sommes dans une langue qui s’écrit gauche à droite ou de droite à gauche ;</li> | |||||
<li>ajuster l’apparence pour distinguer visuellement (mais subtilement) la numérotation du reste du texte.</li> | |||||
</ol> | |||||
<div class="sourceCode" id="cb6"><pre class="sourceCode css"><code class="sourceCode css"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span> | |||||
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * L'attribut `data-numerotation=""` permet de</span></span> | |||||
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * renseigner le numéro d'une ligne.</span></span> | |||||
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * Usage (HTML):</span></span> | |||||
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co"> * <span data-numerotation="25">Une ligne d'un poème</span></span></span> | |||||
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span> | |||||
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="ex">[data-numerotation]</span><span class="in">::before</span> {</span> | |||||
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a> <span class="co">/* </span></span> | |||||
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co"> La propriété `content` des pseudo-éléments permet</span></span> | |||||
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co"> d'afficher du contenu supplémentaire à l'écran,</span></span> | |||||
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co"> mais sans faire partie du «contenu» du document.</span></span> | |||||
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co"> Pour afficher le numéro entre parenthèses:</span></span> | |||||
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="co"> content: '(' attr(data-numerotation) ')';</span></span> | |||||
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span> | |||||
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">content</span>: <span class="fu">attr(</span>data-numerotation<span class="fu">)</span><span class="op">;</span></span> | |||||
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a> </span> | |||||
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a> <span class="co">/* Conservons un espacement minimal avec le texte */</span></span> | |||||
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">margin-inline-start</span>: <span class="dv">1</span><span class="dt">em</span><span class="op">;</span></span> | |||||
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a> </span> | |||||
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a> <span class="co">/* Le numéro «flotte» à la fin de la ligne */</span></span> | |||||
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">float</span>: inline-end<span class="op">;</span></span> | |||||
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a> </span> | |||||
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a> <span class="co">/* Mise en forme qui distingue le numéro du corps */</span></span> | |||||
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">opacity</span>: <span class="dv">.75</span><span class="op">;</span></span> | |||||
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-numerotation.jpg" alt="Résultat visuel d’un poème dont on a balisé la ligne pour la numérotation (ici, la ligne 5)."> | |||||
<figcaption aria-hidden="true">Résultat visuel d’un poème dont on a balisé la ligne pour la numérotation (ici, la ligne 5).</figcaption> | |||||
</figure> | |||||
<p>Puisque nous utilisons un « pseudo-élément » pour afficher la numérotation (c’est-à-dire l’élément <code>::before</code>, qui n’est pas présent dans la source du document, mais qui est ajouté par le navigateur web au moment de la consultation), l’usager qui mettrait le texte en surbrillance avec sa souris pour le copier sélectionnera uniquement le texte, pas le numéro. Pratique !</p> | |||||
<figure> | |||||
<figcaption> | |||||
Démonstration du code dans un cadre embarqué (voir sur <a href="https://codepen.io/loupbrun/pen/qBwreMg" target="_blank" rel="external">CodePen</a>). | |||||
</figcaption> | |||||
</figure> | |||||
<h2 id="penser-les-besoins">Penser les besoins</h2> | |||||
<p>Bref, il s’agit de modéliser le mieux possible les <strong>besoins éditoriaux</strong> pour ensuite – <em>et seulement ensuite !</em> – réunir les ingrédients techniques. (On voit souvent le contraire arriver en pratique : on plaque des solutions existantes sans nécessairement tenir compte des spécificités propres à un problème, un contexte, une communauté.) Il existe souvent plusieurs solutions possibles, plusieurs morceaux de code différents qui produisent, à première vue, un effet similaire ; sauf que les démarches ne se valent pas toutes, et c’est justement en tenant compte du contexte particulier qu’on peut trouver chaussure à son pied (ou à celui des autres).</p> | |||||
</article> | |||||
<hr> | |||||
<footer> | |||||
<p> | |||||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use> | |||||
</svg> Accueil</a> • | |||||
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use> | |||||
</svg> Suivre</a> • | |||||
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use> | |||||
</svg> Pro</a> • | |||||
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use> | |||||
</svg> Email</a> • | |||||
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use> | |||||
</svg> Légal</abbr> | |||||
</p> | |||||
<template id="theme-selector"> | |||||
<form> | |||||
<fieldset> | |||||
<legend><svg class="icon icon-brightness-contrast"> | |||||
<use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use> | |||||
</svg> Thème</legend> | |||||
<label> | |||||
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto | |||||
</label> | |||||
<label> | |||||
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé | |||||
</label> | |||||
<label> | |||||
<input type="radio" value="light" name="chosen-color-scheme"> Clair | |||||
</label> | |||||
</fieldset> | |||||
</form> | |||||
</template> | |||||
</footer> | |||||
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script> | |||||
<script> | |||||
function loadThemeForm(templateName) { | |||||
const themeSelectorTemplate = document.querySelector(templateName) | |||||
const form = themeSelectorTemplate.content.firstElementChild | |||||
themeSelectorTemplate.replaceWith(form) | |||||
form.addEventListener('change', (e) => { | |||||
const chosenColorScheme = e.target.value | |||||
localStorage.setItem('theme', chosenColorScheme) | |||||
toggleTheme(chosenColorScheme) | |||||
}) | |||||
const selectedTheme = localStorage.getItem('theme') | |||||
if (selectedTheme && selectedTheme !== 'undefined') { | |||||
form.querySelector(`[value="${selectedTheme}"]`).checked = true | |||||
} | |||||
} | |||||
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' | |||||
window.addEventListener('load', () => { | |||||
let hasDarkRules = false | |||||
for (const styleSheet of Array.from(document.styleSheets)) { | |||||
let mediaRules = [] | |||||
for (const cssRule of styleSheet.cssRules) { | |||||
if (cssRule.type !== CSSRule.MEDIA_RULE) { | |||||
continue | |||||
} | |||||
// WARNING: Safari does not have/supports `conditionText`. | |||||
if (cssRule.conditionText) { | |||||
if (cssRule.conditionText !== prefersColorSchemeDark) { | |||||
continue | |||||
} | |||||
} else { | |||||
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { | |||||
continue | |||||
} | |||||
} | |||||
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) | |||||
} | |||||
// WARNING: do not try to insert a Rule to a styleSheet you are | |||||
// currently iterating on, otherwise the browser will be stuck | |||||
// in a infinite loop… | |||||
for (const mediaRule of mediaRules) { | |||||
styleSheet.insertRule(mediaRule.cssText) | |||||
hasDarkRules = true | |||||
} | |||||
} | |||||
if (hasDarkRules) { | |||||
loadThemeForm('#theme-selector') | |||||
} | |||||
}) | |||||
</script> | |||||
</body> | |||||
</html> |
title: Écrire ou coder l’éditorial | |||||
url: https://www.lobrassard.net/carnet/2024-03-22-ecrire-coder-editorial.html | |||||
hash_url: bb9ae7eeac8484bf3e675d196b8dc8b9 | |||||
archive_date: 2024-03-25 | |||||
og_image: https://www.lobrassard.net/img/couverture.png | |||||
description: Carnet de recherche de Louis-Olivier Brassard. | |||||
favicon: data:;base64,iVBORw0KGgo= | |||||
language: fr_FR | |||||
<p>À la <a href="https://www.ecrituresnumeriques.ca/" target="_blank" rel="external">Chaire de recherche du Canada sur les écritures numériques</a>, on travaille (notamment) sur la revue <a href="https://www.sens-public.org/"><cite>Sens public</cite></a>. Le défi du jour consistait à adapter la présentation d’extraits de poésie en grec ancien selon le contexte, ainsi qu’une numérotation des lignes sous forme d’annotation. Je ne connais rien au grec ancien ni à la poésie qui s’y rattache (et je me doute que c’est le cas pour bien des gens !), mais il me semble que la démarche éditoriale mise en pratique ici (qui consiste en un dialogue interdisciplinaire) mérite qu’on s’y intéresse un tout petit moment.</p> | |||||
<h2 id="lédition-systématique-des-distiques-élégiaques">L’édition (systématique) des distiques élégiaques</h2> | |||||
<p>Examinons un <em>distique élégiaque</em>, dont voici une source au format texte brut :</p> | |||||
<div class="sourceCode" id="cb1"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>Νῦν μοι "Χαῖρε" λέγεις, ὅτε σου τὸ πρόσωπον ἀπῆλθεν </span> | |||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> κεῖνο τὸ τῆς λύγδου, βάσκανε, λειότερον· </span> | |||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>νῦν μοι προσπαίζεις, ὅτε τὰς τρίχας ἠφάνικάς σου </span> | |||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> τὰς ἐπὶ τοῖς σοβαροῖς αὐχέσι πλαζομένας.</span></code></pre></div> | |||||
<p>Le premier vers est en hexamètre, le deuxième est en pentamètre ; le troisième vers est en hexamètre, le quatrième en pentamètre ; etc. Il y a un patron : les <strong>vers impairs</strong> sont en <strong>hexamètre</strong> ; les <strong>vers pairs</strong> sont en <strong>pentamètre</strong> et devront de surcroît apparaître <strong>en retrait</strong>.</p> | |||||
<p>Au niveau du code, j’ai pensé systématiser la présentation visuelle pour toujours mettre en retrait les lignes paires d’un poème balisé comme tel. Seul hic : il est possible que les éditrices choisissent de tronquer un poème <strong>pour n’en montrer qu’un extrait</strong>, si bien qu’un style automatisant la mise en forme des lignes selon une règle de type pair/impair ne reflètera pas nécessairement la réalité. Il fallait donc que les éditrices puissent avoir la main sur la présentation des poèmes. <em>Faudrait-il leur donner la possibilité de mettre en retrait n’importe quel vers?</em></p> | |||||
<h2 id="wysiwym-à-la-rescousse">WYSIWYM à la rescousse</h2> | |||||
<p>La chaîne éditoriale de <cite>Sens public</cite> repose sur un processus de révision scientifique, mais aussi <strong>technique</strong>, et celui-ci commence à être éprouvé depuis de nombreuses années. Ce processus a pris la forme d’un projet de recherche : <a href="https://stylo.huma-num.fr/">Stylo</a>, un éditeur de texte pensé pour les sciences humaines.</p> | |||||
<p>La plateforme propose de saisir un corps de texte au format <strong>Markdown</strong>. Celui-ci sera traité avec le logiciel <a href="https://pandoc.org/"><code>pandoc</code></a>, créé par le philosophe John MacFarlane. Cela est bon à savoir, car le corps du texte pourra utiliser une syntaxe à la « saveur » de Pandoc. C’est précisément cette saveur qui nous permettra de baliser le texte avec des éléments qui seront pour nous <em>sémantiques</em>, suivant le paradigme du <em lang="en">what you see is what you mean</em> (<abbr title="What You See Is What You Mean" lang="en">WYSIWYM</abbr>) : écrivez, balisez explicitement ce que vous voulez dire.</p> | |||||
<div class="margin-note"> | |||||
<aside> | |||||
<a href="https://pandoc.org/" target="_blank" rel="external">Pandoc</a> est un parseur de documents. C’est un logiciel utilisé en ligne de commande qui permet d’effectuer des conversions entre plusieurs formats de documents (<code>docx</code>, <code>LaTeX</code>, <code>markdown</code>, <code>txt</code>, etc.). | |||||
</aside> | |||||
</div> | |||||
<p>La mise en retrait des vers en pentamètre a une <strong>valeur sémantique</strong> : elle est contextuellement, sémantiquement liée à un type de vers bien spécifique (le <em>pentamètre</em>). Sauf qu’en édition, et éventuellement dans le contexte des <a href="https://gren.openum.ca/" rel="external"><em>éditions critiques</em></a>, mieux vaut prendre le problème à l’envers de « l’intuition visuelle » : ce n’est pas parce qu’un vers est indenté qu’il devient un pentamètre, <strong>mais c’est bien parce que c’est un pentamètre qu’il devra apparaître en retrait</strong>.</p> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-exemple.jpg" alt="Exemple de vers d’un distique élégiaque dans l’édition courante. Les lignes paires sont mises en retrait."> | |||||
<figcaption aria-hidden="true">Exemple de vers d’un distique élégiaque dans l’édition courante. Les lignes paires sont mises en retrait.</figcaption> | |||||
</figure> | |||||
<p>Plutôt que de permettre aux éditrices d’apporter une correction visuelle arbitraire et <em>ad hoc</em>, nous avons opté pour une forme de <strong>balisage sémantique</strong>. Ce qui a semblé naturel, c’était de marquer à l’aide d’un balisage simple et explicite ; en somme, de les <em>nommer</em>. Ainsi, en utilisant la fonctionnalité <a href="https://pandoc.org/MANUAL.html#extension-bracketed_spans"><code>bracked_spans</code></a> de Pandoc pour le Markdown (balisage de type <code>[texte]{.classe}</code>), nous pouvons marquer chaque ligne <em>explicitement</em> comme étant un hexamètre ou un pentamètre.</p> | |||||
<div class="sourceCode" id="cb2"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">Lorem ipsum dolor sit amet si</span><span class="co">]</span>{.hexametre} </span> | |||||
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="co">[</span><span class="ot">Vade ma vaecum</span><span class="co">]</span>{.pentametre} </span> | |||||
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">Lorem ipsum dolor sit amet si</span><span class="co">]</span>{.hexametre} </span> | |||||
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="co">[</span><span class="ot">Vade ma vaecum</span><span class="co">]</span>{.pentametre} </span> | |||||
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span> | |||||
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="co"><!-- </span></span> | |||||
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="co"> Note: la «mise en retrait» effectuée dans le texte</span></span> | |||||
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="co"> source au format Markdown n'a aucun impact dans le rendu</span></span> | |||||
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="co"> final, et n'est utilisé qu'à titre de démonstration.</span></span> | |||||
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="co">--></span></span></code></pre></div> | |||||
<p>Grâce à cette syntaxe, nous avons indiqué un surcroît d’information <em>sans pour l’instant affecter le rendu visuel</em>. On rend ainsi possible la systématisation d’une (éventuelle) règle d’affichage en fonction du <strong>sens</strong>, de la <strong>sémantique du balisage</strong>.</p> | |||||
<h2 id="une-ligne-de-css-mais-non-la-moindre">Une ligne de CSS (mais non la moindre)</h2> | |||||
<p>La syntaxe utilisée pour baliser les vers n’a pas été choisie au hasard : elle permet d’indiquer une « <strong>classe</strong> » sur un passage du texte. Une classe fonctionne un peu comme un système d’étiquettes ou de catégories : chaque élément <em>balisé</em> du document peut en avoir une seule, ou plusieurs.</p> | |||||
<div class="margin-note"> | |||||
<aside> | |||||
Il est possible de combiner les classes pour en avoir plus d’une sur un même passage. Dans notre cas d’étude, nous pourrions par exemple marquer chaque vers en pentamètre de deux classes : une classe <code>.vers</code> et une classe <code>.vers--pentametre</code>, ce qui donnerait le balisage <code>[vers du poème]{.vers.vers--pentametre}</code>. | |||||
</aside> | |||||
</div> | |||||
<p>Une classe permet d’appliquer un <strong>style</strong> de manière systématique pour tous les éléments qui ont cette classe. Pour les documents web, la <strong>présentation visuelle</strong> est décrite séparément de la <strong>structure du document</strong>, grâce aux feuilles de style au langage <abbr lang="en" title="Cascading Style Sheets">CSS</abbr>.</p> | |||||
<p>Pour régler l’affichage des pentamètres en retrait, voici le morceau de solution que je propose (les quelques lignes de code fonctionnel sont précédées de plusieurs lignes de prose qui éclairent le contexte) :</p> | |||||
<div class="sourceCode" id="cb3"><pre class="sourceCode css"><code class="sourceCode css"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span> | |||||
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * Dans l'édition des distiques élégiaques, une ligne</span></span> | |||||
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * en pentamètre est visuellement mise en retrait.</span></span> | |||||
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * Nous y appliquons une «marge» intérieure de `2em`</span></span> | |||||
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co"> * (`1em` correspond à la largeur d'un cadratin).</span></span> | |||||
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="co"> * On utilise la propriété `margin-inline-start` pour</span></span> | |||||
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co"> * appliquer le retrait en fonction de la direction</span></span> | |||||
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="co"> * naturelle du texte dans son contexte linguistique</span></span> | |||||
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="co"> * (de gauche à droite en français, mais cela pourrait</span></span> | |||||
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="co"> * être de droite à gauche en yiddish).</span></span> | |||||
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span> | |||||
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="fu">.pentametre</span> {</span> | |||||
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">margin-inline-start</span>: <span class="dv">2</span><span class="dt">em</span><span class="op">;</span></span> | |||||
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div> | |||||
<p>Et voilà !</p> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-style-retrait.jpg" alt="Résultat visuel d’un poème dont on a balisé les lignes en pentamètre (avec mise en retrait uniforme)."> | |||||
<figcaption aria-hidden="true">Résultat visuel d’un poème dont on a balisé les lignes en pentamètre (avec mise en retrait uniforme).</figcaption> | |||||
</figure> | |||||
<h2 id="un-autre-défi-éditorial-la-numérotation-des-lignes">Un autre défi éditorial : la numérotation des lignes</h2> | |||||
<p>Les éditrices souhaitaient afficher le numéro de certaines lignes, comme cela se fait habituellement dans l’édition de ce type de texte (le <em>distique élégiaque</em>). La numérotation se fait aux lignes par bonds de cinq :</p> | |||||
<div class="sourceCode" id="cb4"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>Νῦν μοι "Χαῖρε" λέγεις, ὅτε σου τὸ πρόσωπον ἀπῆλθεν </span> | |||||
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> κεῖνο τὸ τῆς λύγδου, βάσκανε, λειότερον· </span> | |||||
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>νῦν μοι προσπαίζεις, ὅτε τὰς τρίχας ἠφάνικάς σου </span> | |||||
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> τὰς ἐπὶ τοῖς σοβαροῖς αὐχέσι πλαζομένας. </span> | |||||
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>μηκέτι μοι, μετέωρε, προσέρχεο μηδὲ συνάντα 5 <span class="co"><!-- Numéro --></span> </span> | |||||
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> ἀντὶ ῥόδου γὰρ ἐγὼ τὴν βάτον οὐ δέχομαι. </span></code></pre></div> | |||||
<div class="margin-note"> | |||||
<details open> | |||||
<summary> | |||||
Anti-exemple de numérotation | |||||
</summary> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-mauvaise-numerotation.jpg" alt="Exemple d’une mise en forme des numéros de ligne qu’on voudrait éviter : les numéros ne sont pas alignés (et ont sans doute été insérés à même le texte à citer). Anti-exemple envoyé par Mathilde V."> | |||||
<figcaption aria-hidden="true">Exemple d’une mise en forme des numéros de ligne qu’on voudrait éviter : les numéros ne sont pas alignés (et ont sans doute été insérés à même le texte à citer). Anti-exemple envoyé par Mathilde V.</figcaption> | |||||
</figure> | |||||
</details> | |||||
</div> | |||||
<p>Si les éditrices ajoutent le numéro de vers directement dans le texte, celui-ci apparaîtra au bout de la ligne, <em>comme s’il faisait partie du poème</em>. De plus, les vers n’ont pas la même largeur (ils n’ont pas la même métrique et le nombre de caractères est rarement le même) : les nombres ne seront pas alignés, ce qui n’est ni pratique ni visuellement harmonieux.</p> | |||||
<p>Solution ? Marquer l’intention de numérotation avec une balise « invisible » : un attribut <code>data-numerotation=""</code> inscrit sur la ligne que l’on souhaite numéroter. La numérotation des lignes se fait « manuellement », puisque ce sont souvent des extraits qui sont présentés et qui peuvent être tronqués.</p> | |||||
<div class="sourceCode" id="cb5"><pre class="sourceCode markdown"><code class="sourceCode markdown"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"><!-- </span></span> | |||||
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="co"> La numérotation (qui prendra une mise en forme</span></span> | |||||
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"> particulière) sera appliquée sur la ligne qui est</span></span> | |||||
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co"> marquée de l'attribut `data-numerotation`.</span></span> | |||||
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="co"> --></span></span> | |||||
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span> | |||||
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co">[</span><span class="ot">μηκέτι μοι, μετέωρε, προσέρχεο μηδὲ συνάντα</span><span class="co">]</span>{data-numerotation="15"} </span> | |||||
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> ἀντὶ ῥόδου γὰρ ἐγὼ τὴν βάτον οὐ δέχομαι. </span></code></pre></div> | |||||
<p>La <strong>mise en forme</strong>, traitée séparément de la structure logique du document, se fait aisément avec quelques propriétés <abbr lang="en" title="Cascading Style Sheets">CSS</abbr> :</p> | |||||
<ol type="1"> | |||||
<li>afficher le numéro de la ligne depuis un attribut (une « métadonnée ») ;</li> | |||||
<li>conserver un espace minimal pour que le texte ne vienne jamais se coller au numéro ;</li> | |||||
<li>envoyer le numéro <em>à la fin de la ligne</em>, peu importe si nous sommes dans une langue qui s’écrit gauche à droite ou de droite à gauche ;</li> | |||||
<li>ajuster l’apparence pour distinguer visuellement (mais subtilement) la numérotation du reste du texte.</li> | |||||
</ol> | |||||
<div class="sourceCode" id="cb6"><pre class="sourceCode css"><code class="sourceCode css"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span> | |||||
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * L'attribut `data-numerotation=""` permet de</span></span> | |||||
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co"> * renseigner le numéro d'une ligne.</span></span> | |||||
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * Usage (HTML):</span></span> | |||||
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span></span> | |||||
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="co"> * <span data-numerotation="25">Une ligne d'un poème</span></span></span> | |||||
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span> | |||||
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="ex">[data-numerotation]</span><span class="in">::before</span> {</span> | |||||
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a> <span class="co">/* </span></span> | |||||
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="co"> La propriété `content` des pseudo-éléments permet</span></span> | |||||
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="co"> d'afficher du contenu supplémentaire à l'écran,</span></span> | |||||
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="co"> mais sans faire partie du «contenu» du document.</span></span> | |||||
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a><span class="co"> Pour afficher le numéro entre parenthèses:</span></span> | |||||
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a><span class="co"> content: '(' attr(data-numerotation) ')';</span></span> | |||||
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span> | |||||
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">content</span>: <span class="fu">attr(</span>data-numerotation<span class="fu">)</span><span class="op">;</span></span> | |||||
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a> </span> | |||||
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a> <span class="co">/* Conservons un espacement minimal avec le texte */</span></span> | |||||
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">margin-inline-start</span>: <span class="dv">1</span><span class="dt">em</span><span class="op">;</span></span> | |||||
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a> </span> | |||||
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a> <span class="co">/* Le numéro «flotte» à la fin de la ligne */</span></span> | |||||
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">float</span>: inline-end<span class="op">;</span></span> | |||||
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a> </span> | |||||
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a> <span class="co">/* Mise en forme qui distingue le numéro du corps */</span></span> | |||||
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">opacity</span>: <span class="dv">.75</span><span class="op">;</span></span> | |||||
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div> | |||||
<figure> | |||||
<img src="https://www.lobrassard.net/img/2024-03-22-distique-elegiaque-numerotation.jpg" alt="Résultat visuel d’un poème dont on a balisé la ligne pour la numérotation (ici, la ligne 5)."> | |||||
<figcaption aria-hidden="true">Résultat visuel d’un poème dont on a balisé la ligne pour la numérotation (ici, la ligne 5).</figcaption> | |||||
</figure> | |||||
<p>Puisque nous utilisons un « pseudo-élément » pour afficher la numérotation (c’est-à-dire l’élément <code>::before</code>, qui n’est pas présent dans la source du document, mais qui est ajouté par le navigateur web au moment de la consultation), l’usager qui mettrait le texte en surbrillance avec sa souris pour le copier sélectionnera uniquement le texte, pas le numéro. Pratique !</p> | |||||
<figure> | |||||
<figcaption> | |||||
Démonstration du code dans un cadre embarqué (voir sur <a href="https://codepen.io/loupbrun/pen/qBwreMg" target="_blank" rel="external">CodePen</a>). | |||||
</figcaption> | |||||
</figure> | |||||
<h2 id="penser-les-besoins">Penser les besoins</h2> | |||||
<p>Bref, il s’agit de modéliser le mieux possible les <strong>besoins éditoriaux</strong> pour ensuite – <em>et seulement ensuite !</em> – réunir les ingrédients techniques. (On voit souvent le contraire arriver en pratique : on plaque des solutions existantes sans nécessairement tenir compte des spécificités propres à un problème, un contexte, une communauté.) Il existe souvent plusieurs solutions possibles, plusieurs morceaux de code différents qui produisent, à première vue, un effet similaire ; sauf que les démarches ne se valent pas toutes, et c’est justement en tenant compte du contexte particulier qu’on peut trouver chaussure à son pied (ou à celui des autres).</p> |
<li><a href="/david/cache/2024/956819385548bba6e768563b12edc2d6/" title="Accès à l’article dans le cache local : herbe">herbe</a> (<a href="https://www.la-grange.net/2024/01/24/herbe" title="Accès à l’article original distant : herbe">original</a>)</li> | <li><a href="/david/cache/2024/956819385548bba6e768563b12edc2d6/" title="Accès à l’article dans le cache local : herbe">herbe</a> (<a href="https://www.la-grange.net/2024/01/24/herbe" title="Accès à l’article original distant : herbe">original</a>)</li> | ||||
<li><a href="/david/cache/2024/a988555163e09729b925dbf715ce256c/" title="Accès à l’article dans le cache local : How web bloat impacts users with slow devices">How web bloat impacts users with slow devices</a> (<a href="https://danluu.com/slow-device/" title="Accès à l’article original distant : How web bloat impacts users with slow devices">original</a>)</li> | |||||
<li><a href="/david/cache/2024/2a1235215c277ebb8a0e9acb7ffd91e0/" title="Accès à l’article dans le cache local : drab - A Headless Custom Element Library">drab - A Headless Custom Element Library</a> (<a href="https://drab.robino.dev/" title="Accès à l’article original distant : drab - A Headless Custom Element Library">original</a>)</li> | <li><a href="/david/cache/2024/2a1235215c277ebb8a0e9acb7ffd91e0/" title="Accès à l’article dans le cache local : drab - A Headless Custom Element Library">drab - A Headless Custom Element Library</a> (<a href="https://drab.robino.dev/" title="Accès à l’article original distant : drab - A Headless Custom Element Library">original</a>)</li> | ||||
<li><a href="/david/cache/2024/faa1d8cae94da6838ff9351e5df791ca/" title="Accès à l’article dans le cache local : Make the indie web easier">Make the indie web easier</a> (<a href="https://gilest.org/indie-easy.html" title="Accès à l’article original distant : Make the indie web easier">original</a>)</li> | <li><a href="/david/cache/2024/faa1d8cae94da6838ff9351e5df791ca/" title="Accès à l’article dans le cache local : Make the indie web easier">Make the indie web easier</a> (<a href="https://gilest.org/indie-easy.html" title="Accès à l’article original distant : Make the indie web easier">original</a>)</li> | ||||
<li><a href="/david/cache/2024/f154db1b6eccf69f498b4a31980367bd/" title="Accès à l’article dans le cache local : The most important goal in designing software is understandability">The most important goal in designing software is understandability</a> (<a href="https://ntietz.com/blog/the-most-important-goal-in-designing-software-is-understandability/" title="Accès à l’article original distant : The most important goal in designing software is understandability">original</a>)</li> | <li><a href="/david/cache/2024/f154db1b6eccf69f498b4a31980367bd/" title="Accès à l’article dans le cache local : The most important goal in designing software is understandability">The most important goal in designing software is understandability</a> (<a href="https://ntietz.com/blog/the-most-important-goal-in-designing-software-is-understandability/" title="Accès à l’article original distant : The most important goal in designing software is understandability">original</a>)</li> | ||||
<li><a href="/david/cache/2024/bb9ae7eeac8484bf3e675d196b8dc8b9/" title="Accès à l’article dans le cache local : Écrire ou coder l’éditorial">Écrire ou coder l’éditorial</a> (<a href="https://www.lobrassard.net/carnet/2024-03-22-ecrire-coder-editorial.html" title="Accès à l’article original distant : Écrire ou coder l’éditorial">original</a>)</li> | |||||
<li><a href="/david/cache/2024/99e7d2ba7e4adc69dbf0f1b2858a5248/" title="Accès à l’article dans le cache local : Style with Stateful, Semantic Selectors">Style with Stateful, Semantic Selectors</a> (<a href="https://benmyers.dev/blog/semantic-selectors/" title="Accès à l’article original distant : Style with Stateful, Semantic Selectors">original</a>)</li> | <li><a href="/david/cache/2024/99e7d2ba7e4adc69dbf0f1b2858a5248/" title="Accès à l’article dans le cache local : Style with Stateful, Semantic Selectors">Style with Stateful, Semantic Selectors</a> (<a href="https://benmyers.dev/blog/semantic-selectors/" title="Accès à l’article original distant : Style with Stateful, Semantic Selectors">original</a>)</li> | ||||
<li><a href="/david/cache/2024/30b40ff8034212e070dc7daf2b9406e9/" title="Accès à l’article dans le cache local : an "archives first" approach to mailing lists">an "archives first" approach to mailing lists</a> (<a href="https://public-inbox.org/README.html" title="Accès à l’article original distant : an "archives first" approach to mailing lists">original</a>)</li> | <li><a href="/david/cache/2024/30b40ff8034212e070dc7daf2b9406e9/" title="Accès à l’article dans le cache local : an "archives first" approach to mailing lists">an "archives first" approach to mailing lists</a> (<a href="https://public-inbox.org/README.html" title="Accès à l’article original distant : an "archives first" approach to mailing lists">original</a>)</li> |