Invision Community 4: SEO, prepare for v5 and dormant account notifications Matt November 11, 2024Nov 11
9 hours ago9 hr Management 20 hours ago, KT Walrus said:I won’t bring this up ever again.Yes, we know the PWA needs work. We will make it better, but we really need to get 5.0.0 out. I know it’s very important to you, and I understand why but there are around 200 very important things that 200 people want adding in before final, and we just can’t do it all.Lets get 5.0.0 out and then we can really get into building on existing features.
8 hours ago8 hr Author I consider this navigation issue on the iOS PWA a bug and not a feature.I guess you don’t agree and are not willing to fix this before GA. I really will shut up about this now, but I am extremely frustrated by not getting the UI issues on iPhone addressed for non-technical users before v5 release which is very very important to me for my new v5 based site. I’ll probably have to fix this myself, not use the PWA for users that Add To Home Screen or delay opening the site until this fix is in a point release. Probably I will just fix myself.
8 hours ago8 hr 9 minutes ago, KT Walrus said:I consider this navigation issue on the iOS PWA a bug and not a feature.I guess you don’t agree and are not willing to fix this before GA.I really will shut up about this now, but I am extremely frustrated by not getting the UI issues on iPhone addressed for non-technical users before v5 release which is very very important to me for my new v5 based site.I’ll probably have to fix this myself, not use the PWA for users that Add To Home Screen or delay opening the site until this fix is in a point release. Probably I will just fix myself.We fully understand you are frustrated. We hear you.
8 hours ago8 hr Management It’s not a bug in the sense that there is a refresh function that doesn’t work.I agree that there needs to be a refresh, I just disagree that it’s a few minutes work to do properly (there’s obviously the JS to allow the pull down, then fire a reload() and then the CSS to style it up) but I’d also like to add swipe gestures to allow you to navigate backwards and forwards when on a page as you would on an app.
2 hours ago2 hr Author 6 hours ago, Matt said:It’s not a bug in the sense that there is a refresh function that doesn’t work.I agree that there needs to be a refresh, I just disagree that it’s a few minutes work to do properly (there’s obviously the JS to allow the pull down, then fire a reload() and then the CSS to style it up) but I’d also like to add swipe gestures to allow you to navigate backwards and forwards when on a page as you would on an app.Swipe back and forward already work in the PWA, even from the beginning of PWA support. The only thing missing is the pull to refresh and there are a number of JS code snippets you could use to add pull to refresh to the JS. I don’t think you have to actually code anything. I’ll do a quick search to find you some links.
2 hours ago2 hr Author Here is Claude 3.5 Sonnet’s answer to adding “pull to refresh” to a PWA on iOS. Claude on adding pull to refreshYes! Here's a JavaScript solution to implement pull-to-refresh functionality in Progressive Web Apps on iOS. This code uses the touchstart, touchmove, and touchend events to create a native-feeling pull-to-refresh experience:```javascriptclass PullToRefresh { constructor(options) { this.options = { target: document.querySelector("body"), threshold: 60, // Required pull distance in pixels onRefresh: () => location.reload(), ...options, }; this.startY = 0; this.currentY = 0; this.dragging = false; this.refreshing = false; this.init(); } init() { // Create refresh indicator element this.indicator = document.createElement("div"); this.indicator.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; height: 50px; display: flex; align-items: center; justify-content: center; transform: translateY(-100%); transition: transform 0.2s; `; document.body.prepend(this.indicator); // Add event listeners this.options.target.addEventListener("touchstart", (e) => this.onTouchStart(e) ); this.options.target.addEventListener("touchmove", (e) => this.onTouchMove(e)); this.options.target.addEventListener("touchend", (e) => this.onTouchEnd(e)); } onTouchStart(e) { if (window.scrollY === 0 && !this.refreshing) { this.dragging = true; this.startY = e.touches[0].pageY; } } onTouchMove(e) { if (!this.dragging) return; this.currentY = e.touches[0].pageY; const distance = this.currentY - this.startY; if (distance > 0) { e.preventDefault(); const pullDistance = Math.min(distance * 0.5, this.options.threshold); this.indicator.style.transform = translateY(${pullDistance}px); this.indicator.textContent = pullDistance >= this.options.threshold ? "Release to refresh" : "Pull to refresh"; } } onTouchEnd() { if (!this.dragging) return; this.dragging = false; const distance = this.currentY - this.startY; if (distance >= this.options.threshold) { this.refreshing = true; this.indicator.textContent = "Refreshing..."; this.options.onRefresh().finally(() => { this.refreshing = false; this.indicator.style.transform = "translateY(-100%)"; }); } else { this.indicator.style.transform = "translateY(-100%)"; } }}// Usage exampleconst ptr = new PullToRefresh({ target: document.querySelector("#content"), onRefresh: async () => { // Your refresh logic here await new Promise((resolve) => setTimeout(resolve, 1500)); location.reload(); },});```To use this in your PWA:1. Add the code to your project2. Initialize it with your desired target element and refresh callback3. Add the following to your manifest.json to enable pull-to-refresh:```json{ "display": "standalone", "apple-mobile-web-app-capable": "yes"}```Key features of this implementation:- Works on iOS Safari and when installed as a PWA- Smooth animation with native feel- Customizable threshold and refresh callback- Visual indicator with status messages- Prevents default scroll behavior when pulling- Handles edge cases and cleanupYou can customize the appearance of the refresh indicator by modifying the CSS in the init() method. You might also want to add a loading spinner or other visual feedback during the refresh operation.Note that this is a basic implementation - you might want to add:1. Better error handling2. Animation for the refresh indicator3. Custom styling to match your app's design4. Support for different pull distances5. Better accessibility featuresRemember that while this works well for iOS, Android typically has built-in pull-to-refresh functionality that you might want to preserve, so you may want to add platform detection and only enable this for iOS devices.
2 hours ago2 hr Author Here is Claude’s response to adding pull to refresh in PWAs on iOS. Don’t know if this all works but Claude usually writes good JS for me. Here's the updated version of the PullToRefresh class that only enables the functionality for iOS devices: ```javascript class PullToRefresh { constructor(options) { // First check if we should initialize on this platform if (!this.shouldInitialize()) { return; } this.options = { target: document.querySelector("body"), threshold: 60, onRefresh: () => location.reload(), ...options, }; this.startY = 0; this.currentY = 0; this.dragging = false; this.refreshing = false; this.init(); } shouldInitialize() { // Check for iOS devices const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1); // Check if it's running as a PWA const isStandalone = window.navigator.standalone || window.matchMedia("(display-mode: standalone)").matches; // Only initialize for iOS devices running as PWA return isIOS && isStandalone; } init() { // Create refresh indicator element this.indicator = document.createElement("div"); this.indicator.setAttribute("aria-label", "Pull to refresh indicator"); this.indicator.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; height: 50px; display: flex; align-items: center; justify-content: center; transform: translateY(-100%); transition: transform 0.2s; background-color: #f0f0f0; z-index: 9999; font-family: -apple-system, system-ui, sans-serif; `; document.body.prepend(this.indicator); // Create loading spinner this.spinner = document.createElement("div"); this.spinner.style.cssText = ` display: none; width: 20px; height: 20px; margin-right: 10px; border: 2px solid #007AFF; border-top-color: transparent; border-radius: 50%; animation: spin 1s linear infinite; `; // Add spinner animation const style = document.createElement("style"); style.textContent = ` @keyframes spin { to { transform: rotate(360deg); } } `; document.head.appendChild(style); this.indicator.appendChild(this.spinner); // Add text element this.textElement = document.createElement("span"); this.indicator.appendChild(this.textElement); // Add event listeners with passive: false for better performance this.options.target.addEventListener( "touchstart", (e) => this.onTouchStart(e), { passive: false } ); this.options.target.addEventListener( "touchmove", (e) => this.onTouchMove(e), { passive: false } ); this.options.target.addEventListener("touchend", (e) => this.onTouchEnd(e), { passive: false, }); } updateUI(distance) { const progress = (distance / this.options.threshold) * 100; this.spinner.style.transform = `rotate(${progress * 3.6}deg)`; this.spinner.style.display = "block"; this.textElement.textContent = distance >= this.options.threshold ? "Release to refresh" : "Pull to refresh"; } onTouchStart(e) { if (window.scrollY === 0 && !this.refreshing) { this.dragging = true; this.startY = e.touches[0].pageY; } } onTouchMove(e) { if (!this.dragging) return; this.currentY = e.touches[0].pageY; const distance = this.currentY - this.startY; if (distance > 0) { e.preventDefault(); const pullDistance = Math.min(distance * 0.5, this.options.threshold); this.indicator.style.transform = `translateY(${pullDistance}px)`; this.updateUI(pullDistance); } } onTouchEnd() { if (!this.dragging) return; this.dragging = false; const distance = this.currentY - this.startY; if (distance >= this.options.threshold) { this.refreshing = true; this.spinner.style.display = "block"; this.textElement.textContent = "Refreshing..."; this.spinner.style.animation = "spin 1s linear infinite"; this.options.onRefresh() .finally(() => { this.refreshing = false; this.indicator.style.transform = "translateY(-100%)"; this.spinner.style.animation = "none"; }) .catch((error) => { console.error("Refresh failed:", error); this.textElement.textContent = "Refresh failed"; setTimeout(() => { this.indicator.style.transform = "translateY(-100%)"; }, 1000); }); } else { this.indicator.style.transform = "translateY(-100%)"; } } // Method to manually destroy the instance destroy() { if (this.indicator) { this.indicator.remove(); } if (this.options.target) { this.options.target.removeEventListener("touchstart", this.onTouchStart); this.options.target.removeEventListener("touchmove", this.onTouchMove); this.options.target.removeEventListener("touchend", this.onTouchEnd); } } } // Usage example const ptr = new PullToRefresh({ target: document.querySelector("#content"), onRefresh: async () => { try { // Your refresh logic here await new Promise((resolve) => setTimeout(resolve, 1500)); location.reload(); } catch (error) { console.error("Refresh failed:", error); throw error; // Rethrow to trigger error handling in the class } }, }); ``` This updated version includes: 1. Platform detection through `shouldInitialize()` method 2. Better iOS detection including newer iPads 3. PWA (standalone mode) detection 4. Improved visual feedback with a spinning loader 5. Better error handling 6. Improved accessibility 7. Performance optimizations with passive event listeners 8. Cleanup method through `destroy()` 9. More iOS-native feeling styling To use it: ```javascript // Initialize const ptr = new PullToRefresh({ target: document.querySelector("#content"), threshold: 60, // Optional: customize pull distance onRefresh: async () => { // Your custom refresh logic await fetchNewData(); }, }); // Later, if needed, cleanup ptr.destroy(); ``` The pull-to-refresh will now only initialize on iOS devices running as a PWA. On other platforms, it will silently do nothing, allowing the native pull-to-refresh behavior (if any) to work as expected. Remember to still include the appropriate manifest.json settings: ```json { "display": "standalone", "apple-mobile-web-app-capable": "yes" } ``` This implementation provides a more native-feeling experience for iOS PWA users while not interfering with other platforms' built-in behaviors.