Mathias Bynens

Thoughts on Safari Reader’s generated HTML

Published · tagged with CSS, HTML, iOS, macOS

After my post on how to enable Safari Reader on your site, I decided to play around with it a bit more. As it turns out, every time you click that shiny little Reader button, Safari generates an HTML document and displays it as an overlay to the original document. Let’s have a look at the HTML and CSS used in this process, and find out how we can mess with it.

Safari Reader generates the following HTML:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<base href="{article URL}">
<title><!-- {article title} --></title>
<style id="article-content">
@media print {
.original-url {
display: none;
}
}

h1.title {
font-family: Palatino, Georgia, Times, "Times New Roman", serif;
font-weight: bold;
font-size: 1.33em;
line-height: 1.25em;
}

h1 {
font-size: 1.25em;
}

h2 {
font-size: 1.125em;
}

h3 {
font-size: 1.05em;
}

.page a {
text-decoration: none;
color: rgb(32, 0, 127);
}

.page a:visited {
color: rgb(32, 0, 127);
}

#article img {
/* Float images to the left, so that text will nicely flow around them. */
float: left;
margin-right: 12px;
}

#article img.reader-image-tiny {
/* Don't float very small images -- let them display where they occur in the text. */
float: none;
margin: 0;
}

#article img.reader-image-large {
float: none;
margin: auto;
display: block;
}

.float {
margin: 8px 0;
font-size: 65%;
line-height: 1.4;
text-align: left;
}

.float.left {
float: left;
margin-right: 20px;
}

.float.right {
float: right;
margin-left: 20px;
}

.float.full-width {
float: none;
display: block;
}

.page {
font: 20px Palatino, Georgia, Times, "Times New Roman", serif;
line-height: 160%;
text-align: justify;
}

.page:first-of-type .title {
display: block;
}

.page table {
font-size: 0.9em;
text-align: left;
}

.page.rtl table {
text-align: right;
}

.page-number {
display: none;
}

.title {
display: none;
}
</style>
<style id="reader-ui">
@media screen {
body {
margin: 0;
padding: 0;
background-color: transparent;
-webkit-user-select: none;
}
.cached embed,
.cached applet,
.cached object {
display: none !important;
}
#background {
background-color: rgba(0, 0, 0, 0.8);
-webkit-transform: translateZ(0);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#container {
margin-left: -431px;
left: 50%;
width: 862px;
height: 100%;
position: absolute;
pointer-events: none;
}
#centered {
position: absolute;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.preloading #background {
opacity: 0;
}
.preloading #centered {
-webkit-transform: translate3d(0, 100%, 0);
}
.activating #background {
-webkit-transition: opacity 0.40s ease-out;
opacity: 1.0;
}
.activating #fade-top {
-webkit-animation-name: fadeTopActivationFadeIn;
-webkit-animation-duration: 0.40s;
-webkit-animation-timing-function: ease-out;
}
.activating.skip-transition #fade-top {
-webkit-animation: none !important;
}
@-webkit-keyframes fadeTopActivationFadeIn {
0% {
opacity: 0;
}
80% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.activating.skip-transition #background {
-webkit-transition: none !important;
}
.activating #centered {
-webkit-transition: -webkit-transform 0.40s ease-out;
-webkit-transform: translate3d(0, 0, 0);
}
.activating.skip-transition #centered {
-webkit-transition: none !important;
}
.deactivating #background {
-webkit-transition: opacity 0.40s ease-in;
opacity: 0;
}
.deactivating #fade-top {
opacity: 0;
}
.deactivating #fade-bottom {
-webkit-transition: opacity 0.40s ease-in;
opacity: 0;
}
.deactivating #centered {
-webkit-transition: -webkit-transform 0.40s ease-in;
-webkit-transform: translate3d(0, 100%, 0);
}
.deactivating #hud {
-webkit-transition: opacity 0.25s ease-in;
opacity: 0 !important;
}
#drop-shadow {
position: absolute;
top: 0;
bottom: 0;
width: 800px;
left: 10px;
border-width: 24px 24px;
-webkit-border-image: url(safari-resource:/ReaderDropShadow.png) 24 24 24 24 stretch stretch;
opacity: 0;
}
::-webkit-scrollbar {
width: 21px;
}
::-webkit-scrollbar:horizontal {
display: none;
}
::-webkit-scrollbar-track {
margin-top: 20px;
margin-bottom: 20px;
-webkit-border-image: url(safari-resource:/ReaderTrack.png) 21 0 21 0;
border-width: 21px 0;
}
::-webkit-scrollbar-track:disabled {
display: none;
}
::-webkit-scrollbar-thumb {
-webkit-border-image: url(safari-resource:/ReaderThumb.png) 19 0 19 0;
border-width: 19px 0;
min-height: 40px;
}
#hud {
position: fixed;
width: 314px;
height: 72px;
left: 50%;
margin-left: -157px;
bottom: 30px;
background: rgba(0, 0, 0, 0.75);
-webkit-border-radius: 12px;
z-index: 100;
opacity: 0;
-webkit-transition: opacity 0.75s;
pointer-events: auto;
zoom: reset;
}
#hud button {
display: block;
float: left;
width: 48px;
height: 48px;
padding: 0;
border: none;
margin: 12px 5px;
}
#hud button:first-of-type {
margin-left: 12px;
}
#hud button:last-of-type {
margin-right: 12px;
}
#hud-zoom-out {
background: url(safari-resource:/ReaderHUDZoomOutInactive.png) no-repeat;
}
#hud-zoom-out:active {
background: url(safari-resource:/ReaderHUDZoomOutActive.png) no-repeat;
}
#hud-zoom-in {
background: url(safari-resource:/ReaderHUDZoomInInactive.png) no-repeat;
}
#hud-zoom-in:active {
background: url(safari-resource:/ReaderHUDZoomInActive.png) no-repeat;
}
#hud-mail {
background: url(safari-resource:/ReaderHUDMailContentsInactive.png) no-repeat;
}
#hud-mail:active {
background: url(safari-resource:/ReaderHUDMailContentsActive.png) no-repeat;
}
#hud-print {
background: url(safari-resource:/ReaderHUDPrintInactive.png) no-repeat;
}
#hud-print:active {
background: url(safari-resource:/ReaderHUDPrintActive.png) no-repeat;
}
#hud-exit {
background: url(safari-resource:/ReaderHUDCloseInactive.png) no-repeat;
}
#hud-exit:active {
background: url(safari-resource:/ReaderHUDCloseActive.png) no-repeat;
}
#article {
/* The width of 819px here includes 19px for the WebKit scrollbar’s width. */
/* The padding-right of 8px separates the scrollbar from the article itself. */

position: absolute;
height: 100%;
left: 34px;
width: 819px;
padding-right: 8px;
overflow: scroll;
z-index: 0;
outline: none;
pointer-events: auto;
-webkit-user-select: auto;
-webkit-transform: translateZ(0);
}
.article-fade {
position: absolute;
left: 34px;
height: 36px;
width: 800px;
z-index: 10;
pointer-events: none;
}
#fade-top {
top: 0;
background: url(safari-resource:/ReaderFadeTop.png) repeat-x;
}
#fade-bottom {
bottom: 0;
background: url(safari-resource:/ReaderFadeBottom.png) repeat-x;
}
#resize-indicator {
position: fixed;
bottom: 0;
right: 0;
width: 12px;
height: 12px;
background: url(safari-resource:/TopSitesCornerResize.png);
}
.page:only-of-type .page-number {
display: none;
}
.page-number {
display: block;
font: bold 11px Helvetica, sans-serif;
margin-left: 12px;
color: #B2B2B2;
position: absolute;
right: 10px;
top: 10px;
-webkit-user-select: none;
}
.page:first-of-type {
margin-top: 22px;
}
.page:last-of-type {
margin-bottom: 22px;
}
.page {
width: 658px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
padding: 45px 70px;
color: black;
background: white;
border: 1px solid #c3c3c3;
position: relative;
overflow: hidden;
-webkit-transition: height .5s ease-out;
}
.page.rtl {
direction: rtl;
}
#incoming-page-placeholder {
height: 30px;
margin-bottom: 0;
}
#incoming-page-corner {
position: absolute;
right: 10px;
top: 8px;
}
#incoming-page-spinner {
width: 16px;
height: 16px;
float: right;
background: url(safari-resource:/ReaderSpinner.png);
}
#incoming-page-text {
float: right;
margin-top: 2px;
margin-left: 8px;
color: #B2B2B2;
font: bold 11px Helvetica, sans-serif;
-webkit-user-select: none;
}
#next-page-container {
position: absolute;
display: none;
}
.no-transition {
-webkit-transition: none !important;
}
/* These keyframes try to reproduce the 12 discrete steps seen in a standard system progress indicator. */

@-webkit-keyframes discreteSpinner {
0% {
-webkit-transform: rotate(0deg);
}
8.332% {
-webkit-transform: rotate(0deg);
}
8.333% {
-webkit-transform: rotate(30deg);
}
16.665% {
-webkit-transform: rotate(30deg);
}
16.666% {
-webkit-transform: rotate(60deg);
}
24.999% {
-webkit-transform: rotate(60deg);
}
25.000% {
-webkit-transform: rotate(90deg);
}
33.332% {
-webkit-transform: rotate(90deg);
}
33.333% {
-webkit-transform: rotate(120deg);
}
41.665% {
-webkit-transform: rotate(120deg);
}
41.666% {
-webkit-transform: rotate(150deg);
}
49.999% {
-webkit-transform: rotate(150deg);
}
50.000% {
-webkit-transform: rotate(180deg);
}
58.332% {
-webkit-transform: rotate(180deg);
}
58.333% {
-webkit-transform: rotate(210deg);
}
66.665% {
-webkit-transform: rotate(210deg);
}
66.666% {
-webkit-transform: rotate(240deg);
}
74.999% {
-webkit-transform: rotate(240deg);
}
75.000% {
-webkit-transform: rotate(270deg);
}
83.332% {
-webkit-transform: rotate(270deg);
}
83.333% {
-webkit-transform: rotate(300deg);
}
91.665% {
-webkit-transform: rotate(300deg);
}
91.666% {
-webkit-transform: rotate(330deg);
}
100% {
-webkit-transform: rotate(330deg);
}
}
.animation-discrete-spinner {
-webkit-animation-name: discreteSpinner;
-webkit-animation-duration: 1s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
}
}
</style>
<script src="safari-resource:/localizedStrings.js"></script>
</head>
<!-- The body element will have class="preloading", which gets nullified after the loading is completed. — Mathias -->
<body class=" " onload="ReaderJS.loaded();" onselectstart="setHUDAcceptsPointerEvents(false);" onmouseup="setHUDAcceptsPointerEvents(true);" onblur="didLoseFocus();">
<div id="background" onclick="deactivateAfterAnimation();"></div>
<iframe id="next-page-container"></iframe>
<div id="container">
<div id="resize-indicator" style="display: block;"></div>
<div id="hud" onmouseover="hudMouseOver(event);" onmouseout="hudMouseOut(event);" style="pointer-events: none; opacity: 0;">
<button id="hud-zoom-out" onclick="makeTextSmaller();" title="Zoom out" disabled="true"></button>
<button id="hud-zoom-in" onclick="makeTextLarger();" title="Zoom in" disabled="true"></button>
<button id="hud-mail" onclick="mailArticle();" title="Mail this page" disabled="true"></button>
<button id="hud-print" onclick="printArticle();" title="Print this page" disabled="true"></button>
<button id="hud-exit" onclick="deactivateAfterAnimation();" title="Exit Reader" disabled="true"></button>
</div>
<div id="centered">
<div id="drop-shadow" style="opacity: 1;"></div>
<div id="article" onscroll="articleScrolled();" tabindex="0">
<!-- This node contains a number of 'page' class divs. -->
<div class="page" style="font-family: Palatino, Georgia, Times, 'Times New Roman', serif; font-size: 17px; line-height: 1.4; padding-bottom: 85px;">
<div class="page-number">Page 1 of 1</div>
<h1 class="title"><!-- {article title} --></h1>
<!-- {article HTML} -->
</div>
</div>
</div>
<div id="fade-top" class="article-fade" style="clip: rect(-147px 800px 36px 0px);"></div>
<div id="fade-bottom" class="article-fade" style="clip: rect(0px 800px 36px 0px);"></div>
</div>
</body>
</html>

Note that I’ve replaced all variable content with HTML comments of the form <!-- {article title} --> to make it easier to focus on the generated code Safari uses every time Reader is opened. The raw template (without any indication of where the actual content will go) can be found at file:///Applications/Safari.app/Contents/Resources/Reader.html on Mac.

Some things to note:

  • Even though the HTML5 DOCTYPE is used, the document’s character encoding is still being set the old-school way.
  • The <body> element of a Reader document has an ‘empty’ class attribute (containing only whitespace). When cross-referencing this with the Reader template, we learn that initially this attribute has a value: class="preloading".
  • The pre-loading spinner (as visible when using Reader on a multi-page article) is not a GIF image, but uses [vendor-specific] ‘CSS3’ properties for the rotation. (Look for discreteSpinner in the code.)
  • When Reader is opened, the Web Inspector gets closed automatically. In addition, the ‘Show Web Inspector’ and ‘Show Error Console’ items (found under the ‘Develop’ menu) are disabled. The keyboard shortcuts for these functions (respectively ⌘ + ⌥ + I and ⌘ + ⌥ + C) don’t work either. However, it’s still possible to right-click the Reader document and select ‘Inspect Element’. Doing this will open the Web Inspector anyway, and give you access to all of its tools.

Customizing the Safari Reader UI with JavaScript and CSS

Because Safari Reader is HTML-based, you can easily write your own CSS to be used instead of the default one.

For example, try running the following JavaScript code from the console after opening Reader to greatly improve its usability and general awesomeness:

(function(d) {
var s = d.createElement('style');
var c = '#background{background:#f773b5 url(https://i.imgur.com/bB7aD.jpg)}h1.title,.page{font-family:"Comic Sans MS"!important}';
c += 'h1.title{color:#f773b5}.page{background:rgba(255,255,255,.9);-webkit-animation-name:f;-webkit-animation-duration:5s;';
c += '-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear}@-webkit-keyframes f{';
c += '0%{-webkit-transform:rotate(0) scale(1)}25%{-webkit-transform:rotate(-4deg) scale(.95)}50%{-webkit-transform:rotate(0) scale(1)}';
c += '75%{-webkit-transform:rotate(4deg) scale(.95)}100%{-webkit-transform:rotate(0) scale(1)}}';
s.appendChild(d.createTextNode(c));
d.head.appendChild(s)
}(document));

Much better, right?

How to target the Reader document using JavaScript?

I wanted to make this a bookmarklet, by rewriting the code as a one liner and prefixing it with javascript:, but unfortunately that doesn’t seem to work. When using a bookmarklet in Safari with Reader enabled, document still refers to the original HTML document, not the Reader document. Oddly enough, document does refer to the Reader document when entered in the console.

Here’s the thing — I have no idea how the Reader document can be targeted in JavaScript. It’s not a frame or an iframe; the DOM of the original document doesn’t seem to be altered in any way.

It would be very interesting to find out how Safari Reader documents can be manipulated using JavaScript, without having to use the console.

Any clever ideas?

About me

Hi there! I’m Mathias. I work on Chrome DevTools and the V8 JavaScript engine at Google. HTML, CSS, JavaScript, Unicode, performance, and security get me excited. Follow me on Twitter, Mastodon, and GitHub.

Comments

Tim wrote on :

Good post. I find it ironic that on a post about Safari Reader, I don’t see the reader button in my address bar ;)

Senne wrote on :

How can I run the code from the console when the Web Inspector is disabled?

Senne wrote on :

Senne: Oh… Didn’t read this: “However, it’s still possible to right-click the Reader document and select ‘Inspect Element’. Doing this will open the Web Inspector anyway, and give you access to all of its tools.”

Carl wrote on :

Here’s the thing — I have no idea how the Reader document can be targeted in JavaScript. It’s not a frame or an iframe; the DOM of the original document doesn’t seem to be altered in any way.

It would be very interesting to find out how Safari Reader documents can be manipulated using JavaScript, without having to use the console.

Hi Mathias and thanks for this post.

No ideas, but here’s my opinion of the Reader. I would also like to see a script solution for this. The new “reader” IS very good — it is — BUT, it has a lot of disadvantages too. Such as when the reader page is in action — your page/site is now out of your own control. Nothing what I like anyway.

So, even if it’s nice to be able to give the visitor an ad-free page to read/view/print — it is also eliminating every poor/lousy/halfgood attempt to protect your own content… I mean all those “disable right-click” etc — different solutions to protect images etc. (Yes, I know in the end they all can get it somehow anyway — but they need to work a little — sometimes that enough for them to give up.)

What I would like to see… and have — is a way to be able to control it. Some pages/sites I’d really like to have the “Reader” on — and in other sites I’d like to be able to script the button/function away. Then it is in my control and also more useful.

If there were an ability to contol the reader a little bit better — then we could turn the reader into a great asset when disigning pages/sites.

So what a really wish/hope/suggest for a good Reader is:

  • …a way to script in/out the reader function.

  • …a possibility to add my own CSS-file on the server to control the look for the visitor.

With those two — the Reader could start working as a really nice “light version” or a “print-version” page — and save the time doing one.


What I don’t like about the reader, except is taking over my control is when I want it to work… It’s inconsistent. For a “normal” page with perhaps 1 or 2 columns, and maybe an upper and a lower section — it just choose 1 block and it’s content. Not the page. So in an example of a page with one big content block above a side column to the right and a lower block with additional content to the upper one (like “read more” stuff) it will choose the block containing the most p-tags, header-tags etc…

As for now — I’m very disappointed with the new Reader and in the way it works. So for now I have stopped recommending people/friends to try or switch to Safari… Just because how easy it is now for everyone to “steal” content, but foremost the reason that I can’t control the look of it except my own Reader by editing my own file (Reader.html).

This is a little bit of Microsoft behaviour… when they tell me how and what I can do and choose. Not nice.

Like someone wrote somewhere (can’t remember). We’re the ones to suffer the “fight” between Apple and Google. Not fair.

Sorry for the long post… :)

/Carl

T. Joseph Carter wrote on :

May I suggest that the reason why people want something like the reader is because too many website creators seem to think that 8px, unscalable fonts are quite fashionable? Make your design load quickly, be easy on the eyes, and yes, have readable content without aggravating your viewers and they wouldn’t NEED Safari Reader (or the Readable plugin upon which it appears to be based)!

As a guy who has bad vision, I have come to have a love/hate relationship (mostly without the love) for the majority of the web. I set my fonts to something readable, if you ignore my choice you’re going to irritate me. If you have font size controls and they end up making the page look WORSE instead of better, you’re going to irritate me. If I pull out the old standby of zooming the page with ⌘ - + and your fonts either stay the same size or worse start to shrink rather than grow, you’re going to irritate me! If I have to copy text out of your page because I wind up with line heights less than the font size, you’re going to IRRITATE ME! And if I cannot select and copy text to the clipboard because YOU THINK your content is so precious that it must be protected from actually being used/read/accessed on MY terms rather than your own, YOU’RE GOING TO REALLY START TO TICK ME OFF!

Doing this stuff right is hard. Oh man, I know it — I wound up here looking for info on how to improve Safari Reader for large font users, but I’ve been working hard to learn to make fluid CSS that does the right thing at any base font size you've got. You need a base font of 24 or 36px? You got it! It’s not easy to make your site look good and scale like that.

The fact that so many people don’t even try is why Safari Reader (and the Readability plugin) exist.

Anyway, that’s just my thought on the matter, from a guy whose eyes are bad enough to warrant a white cane.

Marion Delgado wrote on :

I have to say that snippet of JavaScript has transformed my Safari Reader experience in ways no other style transformation has achieved. I recommend everyone follow your advice. It’s criminal Safari didn’t include all of those enhancements, so thank you for your hard work.

Enoch wrote on :

Hey Mathias,

Would you by any chance be able to include the raw file and its assets in GitHub as I am having trouble navigating to it. Also, could you add in that script to that document so that it automatically parses that way.

For some reason, I wish that websites would be plain and simple like what Safari's reader does. Also, sometimes I wish that someone would make a stylesheet that turns markdown-to-HTML content to look like Safari's reader. (Ehemmm!)

Anyway, it would be great if you could do that, Enoch

Leave a comment

Comment on “Thoughts on Safari Reader’s generated HTML”

Your input will be parsed as Markdown.