1class SwapcardWidget { 2 constructor() { 3 this.listenMessage = this.listenMessage.bind(this); 4 this.init = this.init.bind(this); 5 this.updateVisibleArea = this.updateVisibleArea.bind(this); 6 } 7 8 scrollToIframeTop() { 9 if (!this.iframe) return; 10 11 const rect = this.iframe.getBoundingClientRect(); 12 13 const viewportHeight = 14 window.innerHeight || document.documentElement.clientHeight; 15 16 const isTopVisible = 17 rect.top >= this.scrollTopOffset && rect.top <= viewportHeight; 18 19 if (isTopVisible) { 20 return; 21 } 22 23 const iframeTop = rect.top + window.scrollY; 24 25 window.scrollTo({ 26 top: iframeTop - this.scrollTopOffset, 27 behavior: "smooth", 28 }); 29 } 30 31 // The widget grows to the height of its content, so it never scrolls on its 32 // own. It cannot tell which part of it the visitor has on screen, and a 33 // popup would open in the middle of the whole list. We send it the part of 34 // the widget the visitor sees, measured from the top and the bottom edges. 35 sendVisibleArea() { 36 if (!this.iframe || !this.iframe.contentWindow) return; 37 38 const rect = this.iframe.getBoundingClientRect(); 39 40 const viewportHeight = 41 window.innerHeight || document.documentElement.clientHeight; 42 43 const top = Math.min(Math.max(-rect.top, 0), rect.height); 44 45 const bottom = Math.min( 46 Math.max(rect.bottom - viewportHeight, 0), 47 rect.height, 48 ); 49 50 const height = rect.height - top - bottom; 51 52 if (height <= 0) return; 53 54 this.iframe.contentWindow.postMessage( 55 { swapcardWidget: { type: "widget-visible-area", top, bottom, height } }, 56 "*", 57 ); 58 } 59 60 updateVisibleArea() { 61 if (this.visibleAreaFrame) return; 62 63 this.visibleAreaFrame = window.requestAnimationFrame(() => { 64 this.visibleAreaFrame = null; 65 this.sendVisibleArea(); 66 }); 67 } 68 69 watchVisibleArea() { 70 window.addEventListener("scroll", this.updateVisibleArea, { 71 passive: true, 72 }); 73 window.addEventListener("resize", this.updateVisibleArea, { 74 passive: true, 75 }); 76 } 77 78 listenMessage({ autoResize }) { 79 window.addEventListener("message", (event) => { 80 if (!event.data.swapcardWidget) return; 81 82 const { type, height } = event.data.swapcardWidget; 83 84 if (type === "widget-resize" && autoResize) { 85 this.iframe.style.height = `${height}px`; 86 this.updateVisibleArea(); 87 } 88 89 if (type === "widget-loaded") { 90 this.updateVisibleArea(); 91 } 92 93 if (type === "widget-navigate") { 94 this.scrollToIframeTop(); 95 } 96 }); 97 } 98 99 initIframe(iframeId) { 100 this.iframe = document.getElementById(iframeId); 101 } 102 103 init({ 104 iframeId = "sc-widget", 105 autoResize = true, 106 scrollTopOffset = 20, 107 } = {}) { 108 this.initIframe(iframeId); 109 this.listenMessage({ autoResize }); 110 this.watchVisibleArea(); 111 this.scrollTopOffset = scrollTopOffset; 112 } 113} 114 115window._scWidget = new SwapcardWidget();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.