From 128168be58bec5a040eecd8c005109dfde323821 Mon Sep 17 00:00:00 2001 From: Sam Thorogood Date: Thu, 16 Apr 2020 14:42:27 +1000 Subject: [PATCH] update readme, use font var --- .npmignore | 4 +++- README.md | 36 +++++++++++++++++++++++------------- pwacompat.min.js | 26 +++++++++++++------------- src/pwacompat.js | 35 +++++++++++++++++++++++++---------- test/index.html | 8 +++++++- 5 files changed, 71 insertions(+), 38 deletions(-) diff --git a/.npmignore b/.npmignore index 712bfa2..e2c638f 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,6 @@ +.travis.yml suite.* testrunner.* yarn.lock -test/ \ No newline at end of file +test/ +src/suite.* diff --git a/README.md b/README.md index b945a2b..5463567 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,17 @@ [![Build Status](https://travis-ci.org/GoogleChromeLabs/pwacompat.svg?branch=master)](https://travis-ci.org/GoogleChromeLabs/pwacompat) PWACompat is a library that brings the [Web App Manifest](https://developers.google.com/web/fundamentals/web-app-manifest/) to non-compliant browsers for better [Progressive Web Apps](https://en.wikipedia.org/wiki/Progressive_Web_Apps). -This includes creating splash screens for Mobile Safari, and supporting IE/Edge's Pinned Sites feature. +This mostly means creating splash screens and icons for Mobile Safari, as well as supporting IE/Edge's Pinned Sites feature. So, if you've created a `manifest.webmanifest` but want to have wide support everywhere else—through legacy HTML tags for icons and theming—look no further. -Just include the minified script `pwacompat.min.js` (or [bundle/serve it yourself](https://npmjs.com/package/pwacompat)) in your page: +We recommend including it from a CDN to get the latest version, or [bundling it yourself](https://npmjs.com/package/pwacompat): ```html - + ``` -And you're done! 🎉📄 +And you're done^! 🎉📄 For more on the Web App Manifest, read 📖 [how to add a Web App Manifest and mobile-proof your site](https://medium.com/dev-channel/how-to-add-a-web-app-manifest-and-mobile-proof-your-site-450e6e485638), watch 📹 [theming as part of The Standard](https://www.youtube.com/watch?v=5fEMTxpA6BA), or check out 📬 [the Web Fundamentals post on PWACompat](https://developers.google.com/web/updates/2018/07/pwacompat). @@ -22,7 +20,7 @@ For more on the Web App Manifest, read 📖 [how to add a Web App Manifest and m PWACompat takes your regular manifest and enhances other browsers

-# Best Practice & Caveats +# ^Best Practice & Caveats While PWACompat can generate most icons, meta tags etc that your PWA might need, it's best practice to include at least one ``. This is standardized and older browsers, along with search engines, may use it from your page to display an icon. @@ -35,23 +33,35 @@ For example: ``` -If you're looking for the best load performance, you can also defer loading PWACompat until after your site has loaded. -This is the approach taken in [Emojityper](https://github.com/emojityper/emojityper/blob/master/src/loader.js#L8). +You should also consider only loading PWACompat after your site is loaded, as adding your site to a homescreen is a pretty rare operation. +This is the approach taken on [v8.dev](https://github.com/v8/v8.dev/pull/310/files) and [Emojityper](https://github.com/emojityper/emojityper/blob/master/src/loader.js#L8). ## iOS PWACompat looks for a viewport tag which includes `viewport-fit=cover`, such as ``. If this tag is detected, PWACompat will generate a meta tag that makes your PWA load in fullscreen mode—this is particularly useful for devices with a notch. +You can customize the generated splash screen's font by using a CSS Variable. +For example: + +```html + +``` + +This is set directly as a [canvas font](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/font), so you must as a minimum include size _and_ family. +The default value is "24px HelveticaNeue-CondensedBold". + +⚠️ PWACompat won't wait for your fonts to load, so if you're using custom fonts, be sure to only load the library after they're ready. + ### Old Versions Prior [to iOS 12.2](https://twitter.com/mhartington/status/1089293403089784832), Mobile Safari opens external sites in the regular browser, meaning that flows like Oauth won't complete correctly. This [isn't a problem with PWACompat](https://github.com/GoogleChromeLabs/pwacompat/issues/15), but is an issue with PWAs on iOS generally. -Prior to [iOS 11.3](https://medium.com/@firt/pwas-are-coming-to-ios-11-3-cupertino-we-have-a-problem-2ff49fd7d6ea), Mobile Safari would not respect the `start_url` paramater inside the manifest. -If you want to emulate this behavior (and redirect the user to the start page), then you could detect `navigator.standalone` (indicating that your site is loaded in PWA mode) and set a flag in `window.sessionStorage`. -If the flag is not yet set, then you should redirect to your site's start URL. - ## Session Storage PWACompat uses `window.sessionStorage` to cache your site's manifest (and on iOS, any updated icons and generated splash screens). diff --git a/pwacompat.min.js b/pwacompat.min.js index d73fe27..10dedc3 100644 --- a/pwacompat.min.js +++ b/pwacompat.min.js @@ -1,13 +1,13 @@ -function Q(n){var q=0;return function(){return q\'';var e=y([b,window.location]),g=n("manifest");if(g)try{var u=JSON.parse(g);H(u,e)}catch(v){console.warn("PWACompat error",v)}else{var t=new XMLHttpRequest;t.open("GET",b);t.withCredentials="use-credentials"===a.getAttribute("crossorigin");t.onload=function(){try{var v=JSON.parse(t.responseText); -n("manifest",t.responseText);H(v,e)}catch(A){console.warn("PWACompat error",A)}};t.send(null)}}function y(a){for(var b={},e=0;e.6*f)m.fillText(c,I/-2,0),m.translate(0,24*1.2),h.splice(0,d),d=0}return function(){var J=m.canvas.toDataURL();g(l,J);return J}}function g(f,c){var l=document.createElement("link");l.setAttribute("rel","apple-touch-startup-image");l.setAttribute("media","(orientation: "+f+")");l.setAttribute("href", -c);document.head.appendChild(l)}function u(f,c){var l=window.screen,h=e(l.availWidth,l.availHeight,"portrait",f),d=e(l.availHeight,l.availWidth,"landscape",f);window.setTimeout(function(){x.p=h();window.setTimeout(function(){x.l=d();c()},10)},10)}function t(f){function c(){--l||f()}var l=C.length+1;c();C.forEach(function(h){var d=new Image;d.crossOrigin="anonymous";d.onerror=c;d.onload=function(){d.onload=null;h.href=K(d,B,!0);x.i[d.src]=h.href;c()};d.src=h.href})}function v(){n("iOS",JSON.stringify(x))} -function A(){var f=C.shift();if(f){var c=new Image;c.crossOrigin="anonymous";c.onerror=function(){return void A()};c.onload=function(){c.onload=null;u(c,function(){var l=a.background_color&&K(c,B);l?(f.href=l,x.i[c.src]=l,t(v)):v()})};c.src=f.href}else u(null,v)}var r=a.icons||[],p=r.filter(function(f){return(f.f||"").includes("maskable")});r.sort(function(f,c){return w(c)-w(f)});p.sort(function(f,c){return w(c)-w(f)});var C=(0w(f)))return c.rel="apple-touch-icon",E("link",c)}).filter(Boolean);p=document.head.querySelector('meta[name="viewport"]');var U=!!(p&&p.content||"").match(/\bviewport-fit\s*=\s*cover\b/),L=a.display;p=-1!==V.indexOf(L);k("mobile-web-app-capable",p);W(a.theme_color||"black",U);X&&(k("application-name",a.short_name),k("msapplication-tooltip",a.description),k("msapplication-starturl",b(a.start_url||".")),k("msapplication-navbutton-color",a.theme_color),(r=r[0])&&k("msapplication-TileImage", -b(r.src)),k("msapplication-TileColor",a.background_color));document.head.querySelector('[name="theme-color"]')||k("theme-color",a.theme_color);if(D){var B=a.background_color||"#f8f9fa",T=M(B);(r=Y(a.related_applications))&&k("apple-itunes-app","app-id="+r);k("apple-mobile-web-app-capable",p);k("apple-mobile-web-app-title",a.short_name||a.name);if(p=n("iOS"))try{var G=JSON.parse(p);g("portrait",G.p);g("landscape",G.l);C.forEach(function(f){var c=G.i[f.href];c&&(f.href=c)});return}catch(f){}var x={i:{}}; -A()}else r={por:"portrait",lan:"landscape"}[String(a.orientation||"").substr(0,3)]||"",k("x5-orientation",r),k("screen-orientation",r),"fullscreen"===L?(k("x5-fullscreen","true"),k("full-screen","yes")):p&&(k("x5-page-mode","app"),k("browsermode","application"))}function Y(a){var b;(a||[]).filter(function(e){return"itunes"===e.platform}).forEach(function(e){e.id?b=e.id:(e=e.url.match(/id(\d+)/))&&(b=e[1])});return b}function W(a,b){if(D||Z){var e=M(a);if(D)k("apple-mobile-web-app-status-bar-style", -b?"black-translucent":e?"black":"default");else{a:{try{var g=Windows.UI.ViewManagement.ApplicationView.getForCurrentView().titleBar;break a}catch(u){}g=void 0}if(b=g)b.foregroundColor=N(e?"black":"white"),b.backgroundColor=N(a)}}}function N(a){a=O(a);return{r:a[0],g:a[1],b:a[2],a:a[3]}}function O(a){var b=F();b.fillStyle=a;b.fillRect(0,0,1,1);return b.getImageData(0,0,1,1).data||[]}function M(a){a=O(a).map(function(b){b/=255;return.03928>b?b/12.92:Math.pow((b+.055)/1.055,2.4)});return 3\'';var b=y([a,window.location]),e=n("manifest");if(e)try{var g=JSON.parse(e);H(g,b)}catch(u){console.warn("PWACompat error",u)}else{var p=new XMLHttpRequest;p.open("GET",a);p.withCredentials="use-credentials"===A.getAttribute("crossorigin");p.onload=function(){try{var u=JSON.parse(p.responseText); +n("manifest",p.responseText);H(u,b)}catch(v){console.warn("PWACompat error",v)}};p.send(null)}}function y(a){for(var b={},e=0;e.6*f)l.fillText(I,J/-2,0),l.translate(0,1.2*m),d.splice(0,c),c=0}return function(){var K=l.canvas.toDataURL();g(k,K);return K}}function g(f,c){var k=document.createElement("link"); +k.setAttribute("rel","apple-touch-startup-image");k.setAttribute("media","(orientation: "+f+")");k.setAttribute("href",c);document.head.appendChild(k)}function p(f,c){var k=window.screen,m=e(k.availWidth,k.availHeight,"portrait",f),d=e(k.availHeight,k.availWidth,"landscape",f);window.setTimeout(function(){x.p=m();window.setTimeout(function(){x.l=d();c()},10)},10)}function u(f){function c(){--k||f()}var k=C.length+1;c();C.forEach(function(m){var d=new Image;d.crossOrigin="anonymous";d.onerror=c;d.onload= +function(){d.onload=null;m.href=L(d,B,!0);x.i[d.src]=m.href;c()};d.src=m.href})}function v(){n("iOS",JSON.stringify(x))}function M(){var f=C.shift();if(f){var c=new Image;c.crossOrigin="anonymous";c.onerror=function(){return void M()};c.onload=function(){c.onload=null;p(c,function(){var k=a.background_color&&L(c,B);k?(f.href=k,x.i[c.src]=k,u(v)):v()})};c.src=f.href}else p(null,v)}var t=a.icons||[],q=t.filter(function(f){return(f.h||"").includes("maskable")});t.sort(function(f,c){return w(c)-w(f)}); +q.sort(function(f,c){return w(c)-w(f)});var C=(0w(f)))return c.rel="apple-touch-icon",E("link",c)}).filter(Boolean);q=document.head.querySelector('meta[name="viewport"]');var W=!!(q&&q.content||"").match(/\bviewport-fit\s*=\s*cover\b/),N=a.display;q=-1!==X.indexOf(N);h("mobile-web-app-capable",q);Y(a.theme_color||"black",W);Z&&(h("application-name",a.short_name),h("msapplication-tooltip",a.description), +h("msapplication-starturl",b(a.start_url||".")),h("msapplication-navbutton-color",a.theme_color),(t=t[0])&&h("msapplication-TileImage",b(t.src)),h("msapplication-TileColor",a.background_color));document.head.querySelector('[name="theme-color"]')||h("theme-color",a.theme_color);if(D){var B=a.background_color||"#f8f9fa",V=O(B);(t=aa(a.related_applications))&&h("apple-itunes-app","app-id="+t);h("apple-mobile-web-app-capable",q);h("apple-mobile-web-app-title",a.short_name||a.name);if(q=n("iOS"))try{var G= +JSON.parse(q);g("portrait",G.p);g("landscape",G.l);C.forEach(function(f){var c=G.i[f.href];c&&(f.href=c)});return}catch(f){}var x={i:{}};M()}else t={por:"portrait",lan:"landscape"}[String(a.orientation||"").substr(0,3)]||"",h("x5-orientation",t),h("screen-orientation",t),"fullscreen"===N?(h("x5-fullscreen","true"),h("full-screen","yes")):q&&(h("x5-page-mode","app"),h("browsermode","application"))}function aa(a){var b;(a||[]).filter(function(e){return"itunes"===e.platform}).forEach(function(e){e.id? +b=e.id:(e=e.url.match(/id(\d+)/))&&(b=e[1])});return b}function Y(a,b){if(D||ba){var e=O(a);if(D)h("apple-mobile-web-app-status-bar-style",b?"black-translucent":e?"black":"default");else{a:{try{var g=Windows.UI.ViewManagement.ApplicationView.getForCurrentView().titleBar;break a}catch(p){}g=void 0}if(b=g)b.foregroundColor=P(e?"black":"white"),b.backgroundColor=P(a)}}}function P(a){a=Q(a);return{r:a[0],g:a[1],b:a[2],a:a[3]}}function Q(a){var b=F();b.fillStyle=a;b.fillRect(0,0,1,1);return b.getImageData(0, +0,1,1).data||[]}function O(a){a=Q(a).map(function(b){b/=255;return.03928>b?b/12.92:Math.pow((b+.055)/1.055,2.4)});return 3'`; @@ -82,7 +85,7 @@ function unused() { const data = /** @type {!Object} */ (JSON.parse(storedResponse)); process(data, hrefFactory); } catch (err) { - console.warn('PWACompat error', err) + console.warn('PWACompat error', err); } return; } @@ -100,7 +103,7 @@ function unused() { store('manifest', xhr.responseText); process(data, hrefFactory); } catch (err) { - console.warn('PWACompat error', err) + console.warn('PWACompat error', err); } }; xhr.send(null); @@ -272,11 +275,18 @@ function unused() { ctx.fillStyle = backgroundIsLight ? 'white' : 'black'; ctx.font = `${defaultSplashTextSize}px ${defaultFontName}`; + // Set the user-requested font; if it's invalid, the set will fail. + const s = window.getComputedStyle(manifestEl); + ctx.font = s.getPropertyValue('--pwacompat-splash-font'); // blank for old browsers + const title = manifest['name'] || manifest['short_name'] || document.title; - const textWidth = ctx.measureText(title).width; - if (textWidth < width * 0.8) { + const measure = ctx.measureText(title); + const textHeight = (measure.actualBoundingBoxAscent || defaultSplashTextSize); + ctx.translate(0, textHeight); + + if (measure.width < width * 0.8) { // short-circuit, just draw entire string - ctx.fillText(title, textWidth / -2, 0); + ctx.fillText(title, measure.width / -2, 0); } else { // longer wrap case, draw once we have >0.7 width accumulated const words = title.split(/\s+/g); @@ -286,7 +296,7 @@ function unused() { if (i === words.length || measureWidth > width * 0.6) { // render accumulated words ctx.fillText(cand, measureWidth / -2, 0); - ctx.translate(0, (defaultSplashTextSize * 1.2)); + ctx.translate(0, textHeight * 1.2); words.splice(0, i); i = 0; } @@ -295,6 +305,11 @@ function unused() { return () => { const data = ctx.canvas.toDataURL(); + if (debug) { + const img = document.createElement('img'); + img.src = data; + document.body.append(img); + } appendSplash(orientation, data); return data; }; @@ -314,7 +329,7 @@ function unused() { // fetch previous (session) iOS image updates const rendered = store('iOS'); - if (rendered) { + if (!debug && rendered) { try { const prev = /** @type {!Object} */ (JSON.parse(rendered)); appendSplash('portrait', prev['p']); diff --git a/test/index.html b/test/index.html index 0660e33..d5f87fd 100644 --- a/test/index.html +++ b/test/index.html @@ -16,18 +16,24 @@ + PWA Compat - +

This is a test of PWA Compat. Try adding this site to your home screen on an iOS device to see correctly rendered icons and dynamic splash screens! + It uses the uncompiled file, for testing.

Links to local page, page without PWA Compat or an external site.