From 73c51bbb748d2809efea42531dfa216058420351 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 26 Jun 2024 14:47:57 +0100 Subject: [PATCH 1/8] Add visual viewport scrollend event ref page --- .../web/api/visual_viewport_api/index.md | 2 +- files/en-us/web/api/visualviewport/index.md | 3 ++ .../visualviewport/scrollend_event/index.md | 51 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 files/en-us/web/api/visualviewport/scrollend_event/index.md diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index bfbcfdde8b06c16..cda8a67a2524888 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -18,7 +18,7 @@ The mobile web contains two viewports, the layout viewport and the visual viewpo What happens when a web page element needs to be visible on screen regardless of the visible portion of a web page? For example, what if you need a set of image controls to remain on screen regardless of the pinch zoom level of the device? Current browsers vary in how they handle this. The visual viewport lets web developers solve this by positioning elements relative to what's shown on screen. -To access a window's visual viewport, you can obtain a {{domxref("VisualViewport")}} object from the {{domxref("window.visualViewport")}} property. The object includes a set of properties describing the viewport. It also adds two events, `onresize` and `onscroll`, that fire whenever the visual viewport changes. These events allow you to position elements relative to the visual viewport that would normally be anchored to the layout viewport. +To access a window's visual viewport, you can obtain a {{domxref("VisualViewport")}} object from the {{domxref("window.visualViewport")}} property. The object includes a set of properties describing the viewport. It also adds three events, `onresize`, `onscroll`, and `onscrollend`, which fire whenever the visual viewport changes. These events allow you to position elements relative to the visual viewport that would normally be anchored to the layout viewport. ## Interfaces diff --git a/files/en-us/web/api/visualviewport/index.md b/files/en-us/web/api/visualviewport/index.md index 1a8452328c32d50..a8dad227ef5af1b 100644 --- a/files/en-us/web/api/visualviewport/index.md +++ b/files/en-us/web/api/visualviewport/index.md @@ -48,6 +48,9 @@ Listen to these events using {{domxref("EventTarget.addEventListener", "addEvent - {{domxref("VisualViewport/scroll_event", "scroll")}} - : Fired when the visual viewport is scrolled. Also available via the `onscroll` property. +- {{domxref("VisualViewport/scrollend_event", "scrollend")}} + - : Fired when a scrolling operation on the visual viewport ends. + Also available via the `onscrollend` property. ## Examples diff --git a/files/en-us/web/api/visualviewport/scrollend_event/index.md b/files/en-us/web/api/visualviewport/scrollend_event/index.md new file mode 100644 index 000000000000000..e39f6f495656799 --- /dev/null +++ b/files/en-us/web/api/visualviewport/scrollend_event/index.md @@ -0,0 +1,51 @@ +--- +title: "VisualViewport: scrollend event" +short-title: scrollend +slug: Web/API/VisualViewport/scroll_event +page-type: web-api-event +browser-compat: api.VisualViewport.scroll_event +--- + +{{APIRef("Visual Viewport")}} + +The **`scrollend`** event of the {{domxref("VisualViewport")}} interface is fired when a scrolling operation on the visual viewport ends. + +## Syntax + +Use the event name in methods like {{domxref("EventTarget.addEventListener", "addEventListener()")}}, or set an event handler property. + +```js +addEventListener("scrollend", (event) => {}); + +onscrollend = (event) => {}; +``` + +## Event type + +A generic {{domxref("Event")}}. + +## Examples + +You can use the `scrollend` event in an {{domxref("EventTarget.addEventListener", "addEventListener()")}} method: + +```js +visualViewport.addEventListener("scrollend", () => { + // … +}); +``` + +Or use the `onscrollend` event handler property: + +```js +visualViewport.onscrollend = () => { + // … +}; +``` + +## Specifications + +{{Specifications}} + +## Browser compatibility + +{{Compat}} From 570dccc78b84b7efd3e02f3375721bcea3ead5c7 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 26 Jun 2024 14:58:52 +0100 Subject: [PATCH 2/8] correct scrollend linking errors --- files/en-us/web/api/visual_viewport_api/index.md | 2 +- files/en-us/web/api/visualviewport/scrollend_event/index.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index cda8a67a2524888..8ccf7d0de2d2238 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -18,7 +18,7 @@ The mobile web contains two viewports, the layout viewport and the visual viewpo What happens when a web page element needs to be visible on screen regardless of the visible portion of a web page? For example, what if you need a set of image controls to remain on screen regardless of the pinch zoom level of the device? Current browsers vary in how they handle this. The visual viewport lets web developers solve this by positioning elements relative to what's shown on screen. -To access a window's visual viewport, you can obtain a {{domxref("VisualViewport")}} object from the {{domxref("window.visualViewport")}} property. The object includes a set of properties describing the viewport. It also adds three events, `onresize`, `onscroll`, and `onscrollend`, which fire whenever the visual viewport changes. These events allow you to position elements relative to the visual viewport that would normally be anchored to the layout viewport. +To access a window's visual viewport, you can obtain a {{domxref("VisualViewport")}} object from the {{domxref("window.visualViewport")}} property. The object includes a set of properties describing the viewport. It also adds three events, {{domxref("VisualViewport/resize_event", "resize")}}, {{domxref("VisualViewport/scroll_event", "scroll")}}, and {{domxref("VisualViewport/scrollend_event", "scrollend")}}, which fire whenever the visual viewport changes. These events allow you to position elements relative to the visual viewport that would normally be anchored to the layout viewport. ## Interfaces diff --git a/files/en-us/web/api/visualviewport/scrollend_event/index.md b/files/en-us/web/api/visualviewport/scrollend_event/index.md index e39f6f495656799..54a924ac439766d 100644 --- a/files/en-us/web/api/visualviewport/scrollend_event/index.md +++ b/files/en-us/web/api/visualviewport/scrollend_event/index.md @@ -1,9 +1,9 @@ --- title: "VisualViewport: scrollend event" short-title: scrollend -slug: Web/API/VisualViewport/scroll_event +slug: Web/API/VisualViewport/scrollend_event page-type: web-api-event -browser-compat: api.VisualViewport.scroll_event +browser-compat: api.VisualViewport.scrollend_event --- {{APIRef("Visual Viewport")}} From 9d428a36476264dc3612397294cd567a16d8ebcc Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Mon, 8 Jul 2024 09:14:59 +0100 Subject: [PATCH 3/8] Fix for wbamberg review comment --- files/en-us/web/api/visual_viewport_api/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index 8ccf7d0de2d2238..6fd4edd1db7fb4a 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -32,9 +32,9 @@ To access a window's visual viewport, you can obtain a {{domxref("VisualViewport ## Examples -The code below is based on [the sample in the specification](https://github.com/WICG/visual-viewport/blob/gh-pages/examples/fixed-to-viewport.html), though it adds a few things that make it function better. It shows a function called `viewportHandler()`. When called it queries the `offsetLeft` and `height` properties for values it uses in a CSS `translate()` method. You invoke this function by passing it to _both_ event calls. +The code below is based on [the sample in the specification](https://github.com/WICG/visual-viewport/blob/gh-pages/examples/fixed-to-viewport.html), though it adds a few things that make it function better. It shows a function called `viewportHandler()`. When called it queries the `offsetLeft` and `height` properties for values it uses in a CSS `translate()` method. `viewportHandler()` is passed in as a handler for the `resize` and `scroll` events. -One thing that may not be clear in this example is the use of the `pendingUpdate` flag and the call to `requestAnimationFrame()`. The `pendingUpdate` flag serves to prevent multiple invocations of the transform that can occur when `onresize` and `onscroll` fire at the same time. Using `requestAnimationFrame()` ensures that the transform occurs before the next render. +One thing that may not be clear in this example is the use of the `pendingUpdate` flag and the call to `requestAnimationFrame()`. The `pendingUpdate` flag serves to prevent multiple invocations of the transform that can occur when `resize` and `scroll` fire at the same time. Using `requestAnimationFrame()` ensures that the transform occurs before the next render. ```js let pendingUpdate = false; From 96f5ad6aec19fd0320773d1ffd8d435a37d1805e Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 17 Jul 2024 15:20:14 +0100 Subject: [PATCH 4/8] Added useful example and better use case explanations --- .../web/api/visual_viewport_api/index.md | 138 ++++++++++++++---- .../api/visualviewport/resize_event/index.md | 18 +-- .../api/visualviewport/scroll_event/index.md | 18 +-- .../visualviewport/scrollend_event/index.md | 18 +-- 4 files changed, 112 insertions(+), 80 deletions(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index 6fd4edd1db7fb4a..1e923b31aa6c037 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -16,14 +16,16 @@ The **Visual Viewport API** provides an explicit mechanism for querying and modi The mobile web contains two viewports, the layout viewport and the visual viewport. The layout viewport covers all the elements on a page and the visual viewport is what is actually visible on the screen. When the user pinch-zooms into the page, the visual viewport shrinks but the layout viewport is unchanged. User-interface features like the on-screen keyboard (OSK) can shrink the visual viewport without affecting the layout viewport. -What happens when a web page element needs to be visible on screen regardless of the visible portion of a web page? For example, what if you need a set of image controls to remain on screen regardless of the pinch zoom level of the device? Current browsers vary in how they handle this. The visual viewport lets web developers solve this by positioning elements relative to what's shown on screen. +What happens when a web page element needs to be visible on screen regardless of the visible portion of a web page? For example, what if you need a set of image controls to remain on screen regardless of the pinch-zoom level of the device? Current browsers vary in how they handle this. The visual viewport lets web developers solve this by positioning elements relative to what's shown on-screen. -To access a window's visual viewport, you can obtain a {{domxref("VisualViewport")}} object from the {{domxref("window.visualViewport")}} property. The object includes a set of properties describing the viewport. It also adds three events, {{domxref("VisualViewport/resize_event", "resize")}}, {{domxref("VisualViewport/scroll_event", "scroll")}}, and {{domxref("VisualViewport/scrollend_event", "scrollend")}}, which fire whenever the visual viewport changes. These events allow you to position elements relative to the visual viewport that would normally be anchored to the layout viewport. +To access a window's visual viewport, you can obtain a {{domxref("VisualViewport")}} object from the {{domxref("window.visualViewport")}} property. The object includes a set of properties describing the viewport. It also adds three events, {{domxref("VisualViewport/resize_event", "resize")}}, {{domxref("VisualViewport/scroll_event", "scroll")}}, and {{domxref("VisualViewport/scrollend_event", "scrollend")}}, which fire when the visual viewport is resized, scrolls, and finishes a scrolling action, respectively. + +The first two events allow you to position elements relative to the visual viewport as it is scrolled or zoomed, which would normally be anchored to the layout viewport. The `scrollend` event allows you to update an element when a scrolling action is completed. For example, you can use these events to keep an element fixed to the visual viewport as it is pinch-zoomed and scrolled, and update it when scrolling ends. ## Interfaces - {{DOMxRef("VisualViewport")}} - - : Represents the visual viewport for a given window. A window's `VisualViewport` object provides information about the viewport's position and size, and receives the {{domxref("VisualViewport.resize_event", "resize")}} and {{domxref("VisualViewport.scroll_event", "scroll")}} events you can monitor to know when changes occur to the window's viewport. + - : Represents the visual viewport for a given window. A window's `VisualViewport` object provides information about the viewport's position and size, and receives the {{domxref("VisualViewport.resize_event", "resize")}}, {{domxref("VisualViewport.scroll_event", "scroll")}} and {{domxref("VisualViewport.scrollend_event", "scrollend")}} events. ### Extensions to other interfaces @@ -32,40 +34,112 @@ To access a window's visual viewport, you can obtain a {{domxref("VisualViewport ## Examples -The code below is based on [the sample in the specification](https://github.com/WICG/visual-viewport/blob/gh-pages/examples/fixed-to-viewport.html), though it adds a few things that make it function better. It shows a function called `viewportHandler()`. When called it queries the `offsetLeft` and `height` properties for values it uses in a CSS `translate()` method. `viewportHandler()` is passed in as a handler for the `resize` and `scroll` events. +Our [Visual viewport events](https://visual-viewport-events.glitch.me/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in a supporting mobile browser and try pinch-zooming and panning around the boxes. On `resize` and `scroll`, the "Total" box is repositioned to keep the same position relative to the visual viewport. On `scrollend`, it updates to show which boxes are in view, and the sum of their numbers. A more complex application could use these techniques to scroll around map tiles and display relevant information for each one shown on the screen. + +The HTML and CSS for this example is fairly basic, and we won't explain it here for brevity. You can check it out at the example link above. + +In the JavaScript, we start by getting a reference to the `total` box we'll be updating as the page is zoomed and scrolled, and we also define an empty array called `visibleBoxes` that we'll use to store references to which boxes can be seen in the visual viewport when a scroll action finishes. + +```js +const total = document.getElementById("total"); +let visibleBoxes = []; +``` + +Next, we define the two key functions we'll run when the events fire. `scrollUpdater()` will fire on `resize` and `scroll`: this function updates the position of the `total` box relative to the visual viewport by querying the {{domxref("VisualViewport.offsetTop")}} and {{domxref("VisualViewport.offsetLeft")}} properties and using their values to update the values of the relevant {{glossary("inset properties")}}. We also change the `total` box's background color to indicate that something is happening. + +The `scrollEndUpdater()` function will fire on `scrollend`: this returns the `note` box to its original color and invokes other functions — `updateVisibleBoxes()` to detect which boxes are currently showing in the visual viewport and `updateSum()` to display their numbers and the sum total inside `total`. -One thing that may not be clear in this example is the use of the `pendingUpdate` flag and the call to `requestAnimationFrame()`. The `pendingUpdate` flag serves to prevent multiple invocations of the transform that can occur when `resize` and `scroll` fire at the same time. Using `requestAnimationFrame()` ensures that the transform occurs before the next render. +```js +const scrollUpdater = () => { + total.style.top = `${visualViewport.offsetTop + 10}px`; + total.style.left = `${visualViewport.offsetLeft + 10}px`; + total.style.background = "yellow"; +}; + +const scrollendUpdater = () => { + total.style.background = "lime"; + updateVisibleBoxes(); + updateSum(); +}; +``` + +The next two functions are: + +- `isVisible()`: This takes a DOM element as input and calculates its left, top, right, and bottom edge positions. It then calculates the lower and upper horizontal and vertical bounds inside which the box will be visible inside the visual viewport. The function then tests to see if the box is within those bounds, horizontally and vertically, calculating a `true`/`false` value for each. Both have to be `true` for the function return value to be `true`. +- `updateVisibleBoxes()`: This gets a reference to all the grid boxes, and for each one, runs `isVisible()` to check whether the box is visible. It pushes the visible boxes to the `visibleBoxes` array after emptying it. ```js -let pendingUpdate = false; - -function viewportHandler(event) { - if (pendingUpdate) return; - pendingUpdate = true; - - requestAnimationFrame(() => { - pendingUpdate = false; - const layoutViewport = document.getElementById("layoutViewport"); - - // Since the bar is position: fixed we need to offset it by the - // visual viewport's offset from the layout viewport origin. - const viewport = event.target; - const offsetLeft = viewport.offsetLeft; - const offsetTop = - viewport.height - - layoutViewport.getBoundingClientRect().height + - viewport.offsetTop; - - // You could also do this by setting style.left and style.top if you - // use width: 100% instead. - bottomBar.style.transform = `translate(${offsetLeft}px, ${offsetTop}px) scale(${ - 1 / viewport.scale - })`; - }); +function IsVisible(box) { + const x = box.offsetLeft, + y = box.offsetTop; + const right = x + box.offsetWidth, + bottom = y + box.offsetHeight; + + const horLowerBound = window.scrollX + visualViewport.offsetLeft; + const horUpperBound = + window.scrollX + visualViewport.offsetLeft + visualViewport.width; + let hor = + (x > horLowerBound && x < horUpperBound) || + (right > horLowerBound && right < horUpperBound); + + const verLowerBound = window.scrollY + visualViewport.offsetTop; + const verUpperBound = + window.scrollY + visualViewport.offsetTop + visualViewport.height; + let ver = + (y > verLowerBound && y < verUpperBound) || + (bottom > verLowerBound && bottom < verUpperBound); + + return hor && ver; } -window.visualViewport.addEventListener("scroll", viewportHandler); -window.visualViewport.addEventListener("resize", viewportHandler); +function updateVisibleBoxes() { + const boxes = document.querySelectorAll(".gridbox"); + visibleBoxes = []; + + for (const box of boxes) { + if (IsVisible(box)) { + visibleBoxes.push(box); + } + } +} +``` + +Next, the `updateSum()` function calculates the total of the visible box's numbers and stores each individual number in an array. It uses these to update the text displayed in the `total` box. + +```js +function updateSum() { + let sumTotal = 0; + let summands = []; + + for (const box of visibleBoxes) { + console.log(`${box.id} is visible`); + + const n = parseInt(box.innerText); + + sumTotal += n; + summands.push(n); + } + + total.innerText = `Total: ${summands.join(" + ")} = ${sumTotal}`; +} +``` + +Now we set event handler properties of both the visual viewport and the {{domxref("Window")}} object to run the key functions at the appropriate times on both mobile and desktop: `scrollUpdater()` will fire on `resize` and `scroll`, while `scrollEndUpdater()` will fire on `scrollend`. + +```js +visualViewport.onresize = scrollUpdater; +visualViewport.onscroll = scrollUpdater; +visualViewport.onscrollend = scrollendUpdater; +window.onresize = scrollUpdater; +window.onscroll = scrollUpdater; +window.onscrollend = scrollendUpdater; +``` + +Finally, we run the `scrollUpdater()` and `scrollEndUpdater()` functions so that the `total` box will show the results of the initial page state when it first loads. + +```js +updateVisibleBoxes(); +updateSum(); ``` ## Specifications diff --git a/files/en-us/web/api/visualviewport/resize_event/index.md b/files/en-us/web/api/visualviewport/resize_event/index.md index d158e75636a86a4..c791c1091325863 100644 --- a/files/en-us/web/api/visualviewport/resize_event/index.md +++ b/files/en-us/web/api/visualviewport/resize_event/index.md @@ -8,7 +8,7 @@ browser-compat: api.VisualViewport.resize_event {{APIRef("Visual Viewport")}} -The **`resize`** event of the {{domxref("VisualViewport")}} interface is fired when the visual viewport is resized. +The **`resize`** event of the {{domxref("VisualViewport")}} interface is fired when the visual viewport is resized. This allows you to position elements relative to the visual viewport as it is zoomed, which would normally be anchored to the layout viewport. ## Syntax @@ -26,21 +26,7 @@ A generic {{domxref("Event")}}. ## Examples -You can use the `resize` event in an {{domxref("EventTarget.addEventListener", "addEventListener()")}} method: - -```js -visualViewport.addEventListener("resize", () => { - // … -}); -``` - -Or use the `onresize` event handler property: - -```js -visualViewport.onresize = () => { - // … -}; -``` +See the [Visual Viewport API](/en-US/docs/Web/API/Visual_Viewport_API#examples) landing page for a usage demo. ## Specifications diff --git a/files/en-us/web/api/visualviewport/scroll_event/index.md b/files/en-us/web/api/visualviewport/scroll_event/index.md index 223fdaf3364c93e..08ad0204e0e91a9 100644 --- a/files/en-us/web/api/visualviewport/scroll_event/index.md +++ b/files/en-us/web/api/visualviewport/scroll_event/index.md @@ -8,7 +8,7 @@ browser-compat: api.VisualViewport.scroll_event {{APIRef("Visual Viewport")}} -The **`scroll`** event of the {{domxref("VisualViewport")}} interface is fired when the visual viewport is scrolled. +The **`scroll`** event of the {{domxref("VisualViewport")}} interface is fired when the visual viewport is scrolled. This allows you to position elements relative to the visual viewport as it is scrolled, which would normally be anchored to the layout viewport. ## Syntax @@ -26,21 +26,7 @@ A generic {{domxref("Event")}}. ## Examples -You can use the `scroll` event in an {{domxref("EventTarget.addEventListener", "addEventListener()")}} method: - -```js -visualViewport.addEventListener("scroll", () => { - // … -}); -``` - -Or use the `onscroll` event handler property: - -```js -visualViewport.onscroll = () => { - // … -}; -``` +See the [Visual Viewport API](/en-US/docs/Web/API/Visual_Viewport_API#examples) landing page for a usage demo. ## Specifications diff --git a/files/en-us/web/api/visualviewport/scrollend_event/index.md b/files/en-us/web/api/visualviewport/scrollend_event/index.md index 54a924ac439766d..d70e89503642cfd 100644 --- a/files/en-us/web/api/visualviewport/scrollend_event/index.md +++ b/files/en-us/web/api/visualviewport/scrollend_event/index.md @@ -8,7 +8,7 @@ browser-compat: api.VisualViewport.scrollend_event {{APIRef("Visual Viewport")}} -The **`scrollend`** event of the {{domxref("VisualViewport")}} interface is fired when a scrolling operation on the visual viewport ends. +The **`scrollend`** event of the {{domxref("VisualViewport")}} interface is fired when a scrolling operation on the visual viewport ends. This allows you to update an element when a scrolling action is completed. For example, you could use the {{domxref("VisualViewport/resize_event", "resize")}} and {{domxref("VisualViewport/scroll_event", "scroll")}} events to keep an element fixed to the visual viewport as it is pinch-zoomed and scrolled, and update it with new content when scrolling ends using `scrollend`. ## Syntax @@ -26,21 +26,7 @@ A generic {{domxref("Event")}}. ## Examples -You can use the `scrollend` event in an {{domxref("EventTarget.addEventListener", "addEventListener()")}} method: - -```js -visualViewport.addEventListener("scrollend", () => { - // … -}); -``` - -Or use the `onscrollend` event handler property: - -```js -visualViewport.onscrollend = () => { - // … -}; -``` +See the [Visual Viewport API](/en-US/docs/Web/API/Visual_Viewport_API#examples) landing page for a usage demo. ## Specifications From 63cd253947e17663b0f1d879bfdfc06f721af607 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Sat, 3 Aug 2024 12:47:24 +0100 Subject: [PATCH 5/8] fixes for wbamberg 2nd round review comments --- .../web/api/visual_viewport_api/index.md | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index 1e923b31aa6c037..3002940652f8415 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -34,15 +34,38 @@ The first two events allow you to position elements relative to the visual viewp ## Examples -Our [Visual viewport events](https://visual-viewport-events.glitch.me/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in a supporting mobile browser and try pinch-zooming and panning around the boxes. On `resize` and `scroll`, the "Total" box is repositioned to keep the same position relative to the visual viewport. On `scrollend`, it updates to show which boxes are in view, and the sum of their numbers. A more complex application could use these techniques to scroll around map tiles and display relevant information for each one shown on the screen. +Our [Visual viewport events](https://visual-viewport-events.glitch.me/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in a supporting mobile browser and try pinch-zooming and panning around the displayed grid of boxes. On `resize` and `scroll`, the "Total" box is repositioned to keep the same position relative to the visual viewport. On `scrollend`, it updates to show which boxes are in view, and the sum of their numbers. A more complex application could use these techniques to scroll around map tiles and display relevant information for each one shown on the screen. + +The example HTML can be seen below. The "Total" box is represented by the {{htmlelement("div")}} with the `id` of `total`. The grid container is represented by the `grid` `
`, and each individual box is represented by a `gridbox` `
`. + +```html +

+ On a mobile device, try pinch-zooming and pan around the boxes. On scrollend, + the "Total" box will update to show which boxes are in view, and the sum of + their numbers. +

+
+
+
1
+
10
+
3
+
8
+
5
+
6
+
7
+
4
+
9
+
2
+
+``` -The HTML and CSS for this example is fairly basic, and we won't explain it here for brevity. You can check it out at the example link above. +We won't explain the example's CSS for the sake of brevity — it is not important for understanding the demo. You can check it out at the example link above. In the JavaScript, we start by getting a reference to the `total` box we'll be updating as the page is zoomed and scrolled, and we also define an empty array called `visibleBoxes` that we'll use to store references to which boxes can be seen in the visual viewport when a scroll action finishes. ```js const total = document.getElementById("total"); -let visibleBoxes = []; +const visibleBoxes = []; ``` Next, we define the two key functions we'll run when the events fire. `scrollUpdater()` will fire on `resize` and `scroll`: this function updates the position of the `total` box relative to the visual viewport by querying the {{domxref("VisualViewport.offsetTop")}} and {{domxref("VisualViewport.offsetLeft")}} properties and using their values to update the values of the relevant {{glossary("inset properties")}}. We also change the `total` box's background color to indicate that something is happening. @@ -69,35 +92,35 @@ The next two functions are: - `updateVisibleBoxes()`: This gets a reference to all the grid boxes, and for each one, runs `isVisible()` to check whether the box is visible. It pushes the visible boxes to the `visibleBoxes` array after emptying it. ```js -function IsVisible(box) { - const x = box.offsetLeft, - y = box.offsetTop; - const right = x + box.offsetWidth, - bottom = y + box.offsetHeight; +function isVisible(box) { + const x = box.offsetLeft; + const y = box.offsetTop; + const right = x + box.offsetWidth; + const bottom = y + box.offsetHeight; const horLowerBound = window.scrollX + visualViewport.offsetLeft; const horUpperBound = window.scrollX + visualViewport.offsetLeft + visualViewport.width; - let hor = + const horizontal = (x > horLowerBound && x < horUpperBound) || (right > horLowerBound && right < horUpperBound); const verLowerBound = window.scrollY + visualViewport.offsetTop; const verUpperBound = window.scrollY + visualViewport.offsetTop + visualViewport.height; - let ver = + const vertical = (y > verLowerBound && y < verUpperBound) || (bottom > verLowerBound && bottom < verUpperBound); - return hor && ver; + return horizontal && vertical; } function updateVisibleBoxes() { const boxes = document.querySelectorAll(".gridbox"); - visibleBoxes = []; + visibleBoxes.length = 0; for (const box of boxes) { - if (IsVisible(box)) { + if (isVisible(box)) { visibleBoxes.push(box); } } @@ -109,7 +132,7 @@ Next, the `updateSum()` function calculates the total of the visible box's numbe ```js function updateSum() { let sumTotal = 0; - let summands = []; + const summands = []; for (const box of visibleBoxes) { console.log(`${box.id} is visible`); @@ -124,7 +147,10 @@ function updateSum() { } ``` -Now we set event handler properties of both the visual viewport and the {{domxref("Window")}} object to run the key functions at the appropriate times on both mobile and desktop: `scrollUpdater()` will fire on `resize` and `scroll`, while `scrollEndUpdater()` will fire on `scrollend`. +Now we set event handler properties on both the visual viewport and the {{domxref("Window")}} object to run the key functions at the appropriate times on both mobile and desktop: + +- We set the handlers on `window` so that the `total` box position and contents will update on conventional window scrolling operations, for example when you scroll the page on a desktop browser. +- We set the handlers on `visualViewport` so that the `total` box position and contents will update on visual viewport scrolling/zooming operations, for example when you pinch-zoom and then scroll the page on a mobile browser. ```js visualViewport.onresize = scrollUpdater; @@ -135,6 +161,8 @@ window.onscroll = scrollUpdater; window.onscrollend = scrollendUpdater; ``` +`scrollUpdater()` will fire on `resize` and `scroll`, while `scrollEndUpdater()` will fire on `scrollend`. + Finally, we run the `scrollUpdater()` and `scrollEndUpdater()` functions so that the `total` box will show the results of the initial page state when it first loads. ```js From d42eddbe7b6a09271207582e6f15fd66a3328ae4 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Mon, 26 Aug 2024 13:23:44 +0100 Subject: [PATCH 6/8] Update code explanation as the demo has changed --- .../web/api/visual_viewport_api/index.md | 127 +++++------------- 1 file changed, 35 insertions(+), 92 deletions(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index 3002940652f8415..ed7ea5dd091ad76 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -34,123 +34,73 @@ The first two events allow you to position elements relative to the visual viewp ## Examples -Our [Visual viewport events](https://visual-viewport-events.glitch.me/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in a supporting mobile browser and try pinch-zooming and panning around the displayed grid of boxes. On `resize` and `scroll`, the "Total" box is repositioned to keep the same position relative to the visual viewport. On `scrollend`, it updates to show which boxes are in view, and the sum of their numbers. A more complex application could use these techniques to scroll around map tiles and display relevant information for each one shown on the screen. +Our [Visual viewport events](https://visual-viewport-events.glitch.me/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in supporting desktop and mobile browsers and try scrolling around the displayed grid of boxes. Also try pinch-zooming on the mobile browser. On `resize` and `scroll`, the information box is repositioned to keep the same position relative to the visual viewport, and the viewport and scroll information it shows is updated. Also, on `resize` and `scroll` we change the box color to indicate something is happening, changing it back on `scrollend`. -The example HTML can be seen below. The "Total" box is represented by the {{htmlelement("div")}} with the `id` of `total`. The grid container is represented by the `grid` `
`, and each individual box is represented by a `gridbox` `
`. +You'll find that on desktop browsers the {{domxref("Window.scrollX")}} and {{domxref("Window.scrollY")}} values are updated as the window is scrolled — the visual viewport position does not change. On mobile browsers however, the {{domxref("VisualViewport.offsetLeft")}} and {{domxref("VisualViewport.offsetTop")}} values are generally updated — it is usually the visual viewport that changes rather than the window position. + +The example HTML can be seen below. The information box is represented by a {{htmlelement("div")}} with an `id` of `output`. ```html -

- On a mobile device, try pinch-zooming and pan around the boxes. On scrollend, - the "Total" box will update to show which boxes are in view, and the sum of - their numbers. +

+ Try scrolling around on a desktop and a mobile browser to see how the reported + values change. Also try pinch/zooming on a mobile browser to see the effect.

-
-
-
1
-
10
-
3
-
8
-
5
-
6
-
7
-
4
-
9
-
2
+
+

+
+

``` We won't explain the example's CSS for the sake of brevity — it is not important for understanding the demo. You can check it out at the example link above. -In the JavaScript, we start by getting a reference to the `total` box we'll be updating as the page is zoomed and scrolled, and we also define an empty array called `visibleBoxes` that we'll use to store references to which boxes can be seen in the visual viewport when a scroll action finishes. +In the JavaScript, we start by getting references to the information box we'll be updating as the page is zoomed and scrolled, as well as the two paragraphs contained within it. The first one will contain reported {{domxref("VisualViewport.offsetLeft")}} and {{domxref("VisualViewport.offsetTop")}} values, while the second one will contain reported {{domxref("Window.scrollX")}} and {{domxref("Window.scrollY")}} values. ```js -const total = document.getElementById("total"); -const visibleBoxes = []; +const output = document.getElementById("output"); +const visualInfo = document.getElementById("visual-info"); +const windowInfo = document.getElementById("window-info"); ``` -Next, we define the two key functions we'll run when the events fire. `scrollUpdater()` will fire on `resize` and `scroll`: this function updates the position of the `total` box relative to the visual viewport by querying the {{domxref("VisualViewport.offsetTop")}} and {{domxref("VisualViewport.offsetLeft")}} properties and using their values to update the values of the relevant {{glossary("inset properties")}}. We also change the `total` box's background color to indicate that something is happening. +Next, we define the two key functions we'll run when the events fire: -The `scrollEndUpdater()` function will fire on `scrollend`: this returns the `note` box to its original color and invokes other functions — `updateVisibleBoxes()` to detect which boxes are currently showing in the visual viewport and `updateSum()` to display their numbers and the sum total inside `total`. +- `scrollUpdater()` will fire on `resize` and `scroll`: this function updates the position of the information box relative to the visual viewport by querying the {{domxref("VisualViewport.offsetTop")}} and {{domxref("VisualViewport.offsetLeft")}} properties and using their values to update the values of the relevant {{glossary("inset properties")}}. We also change the information box's background color to indicate that something is happening, and run the `updateText()` function to update the values shown in the box. +- The `scrollEndUpdater()` function will fire on `scrollend`: this returns the information box to its original color and runs the `updateText()` function to make sure the latest values are shown on `scrollend`. ```js const scrollUpdater = () => { - total.style.top = `${visualViewport.offsetTop + 10}px`; - total.style.left = `${visualViewport.offsetLeft + 10}px`; - total.style.background = "yellow"; + output.style.top = `${visualViewport.offsetTop + 10}px`; + output.style.left = `${visualViewport.offsetLeft + 10}px`; + output.style.background = "yellow"; + updateText(); }; const scrollendUpdater = () => { - total.style.background = "lime"; - updateVisibleBoxes(); - updateSum(); + output.style.background = "lime"; + updateText(); }; ``` -The next two functions are: - -- `isVisible()`: This takes a DOM element as input and calculates its left, top, right, and bottom edge positions. It then calculates the lower and upper horizontal and vertical bounds inside which the box will be visible inside the visual viewport. The function then tests to see if the box is within those bounds, horizontally and vertically, calculating a `true`/`false` value for each. Both have to be `true` for the function return value to be `true`. -- `updateVisibleBoxes()`: This gets a reference to all the grid boxes, and for each one, runs `isVisible()` to check whether the box is visible. It pushes the visible boxes to the `visibleBoxes` array after emptying it. +The `updateText()` function looks like so — it sets the {{domxref("HTMLElement.innerText()")}} of the first paragraph to show the current `VisualViewport.offsetLeft` and `VisualViewport.offsetTop` values, and the `HTMLElement.innerText()` of the second paragraph to show the current `Window.scrollX` and `Window.scrollY` values. After defining `updateText()`, we immediately invoke it so that the information box displays correctly on page load. ```js -function isVisible(box) { - const x = box.offsetLeft; - const y = box.offsetTop; - const right = x + box.offsetWidth; - const bottom = y + box.offsetHeight; - - const horLowerBound = window.scrollX + visualViewport.offsetLeft; - const horUpperBound = - window.scrollX + visualViewport.offsetLeft + visualViewport.width; - const horizontal = - (x > horLowerBound && x < horUpperBound) || - (right > horLowerBound && right < horUpperBound); - - const verLowerBound = window.scrollY + visualViewport.offsetTop; - const verUpperBound = - window.scrollY + visualViewport.offsetTop + visualViewport.height; - const vertical = - (y > verLowerBound && y < verUpperBound) || - (bottom > verLowerBound && bottom < verUpperBound); - - return horizontal && vertical; +function updateText() { + visualInfo.innerText = `Visual viewport left: ${visualViewport.offsetLeft.toFixed(2)} + top: ${visualViewport.offsetTop.toFixed(2)}`; + windowInfo.innerText = `Window scrollX: ${window.scrollX.toFixed(2)} + scrollY: ${window.scrollY.toFixed(2)}`; } -function updateVisibleBoxes() { - const boxes = document.querySelectorAll(".gridbox"); - visibleBoxes.length = 0; - - for (const box of boxes) { - if (isVisible(box)) { - visibleBoxes.push(box); - } - } -} +updateText(); ``` -Next, the `updateSum()` function calculates the total of the visible box's numbers and stores each individual number in an array. It uses these to update the text displayed in the `total` box. - -```js -function updateSum() { - let sumTotal = 0; - const summands = []; - - for (const box of visibleBoxes) { - console.log(`${box.id} is visible`); - - const n = parseInt(box.innerText); - - sumTotal += n; - summands.push(n); - } - - total.innerText = `Total: ${summands.join(" + ")} = ${sumTotal}`; -} -``` +> [!NOTE] +> We truncate all values to two decimal places using the {{jsxref("Number.toFixed()")}} method because some browsers display them as a subpixel value, potentially with a large number of decimal places. Now we set event handler properties on both the visual viewport and the {{domxref("Window")}} object to run the key functions at the appropriate times on both mobile and desktop: -- We set the handlers on `window` so that the `total` box position and contents will update on conventional window scrolling operations, for example when you scroll the page on a desktop browser. -- We set the handlers on `visualViewport` so that the `total` box position and contents will update on visual viewport scrolling/zooming operations, for example when you pinch-zoom and then scroll the page on a mobile browser. +- We set the handlers on `window` so that the information box position and contents will update on conventional window scrolling operations, for example when you scroll the page on a desktop browser. +- We set the handlers on `visualViewport` so that the information box position and contents will update on visual viewport scrolling/zooming operations, for example when you pinch-zoom and then scroll the page on a mobile browser. ```js visualViewport.onresize = scrollUpdater; @@ -163,13 +113,6 @@ window.onscrollend = scrollendUpdater; `scrollUpdater()` will fire on `resize` and `scroll`, while `scrollEndUpdater()` will fire on `scrollend`. -Finally, we run the `scrollUpdater()` and `scrollEndUpdater()` functions so that the `total` box will show the results of the initial page state when it first loads. - -```js -updateVisibleBoxes(); -updateSum(); -``` - ## Specifications {{Specifications}} From 9a2af45b789aeae387bb3feb63cea6df62fbb0f9 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Tue, 27 Aug 2024 10:36:03 +0100 Subject: [PATCH 7/8] Update files/en-us/web/api/visual_viewport_api/index.md Co-authored-by: wbamberg --- files/en-us/web/api/visual_viewport_api/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index ed7ea5dd091ad76..0fca0b6a9c612ea 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -81,7 +81,7 @@ const scrollendUpdater = () => { }; ``` -The `updateText()` function looks like so — it sets the {{domxref("HTMLElement.innerText()")}} of the first paragraph to show the current `VisualViewport.offsetLeft` and `VisualViewport.offsetTop` values, and the `HTMLElement.innerText()` of the second paragraph to show the current `Window.scrollX` and `Window.scrollY` values. After defining `updateText()`, we immediately invoke it so that the information box displays correctly on page load. +The `updateText()` function looks like so — it sets the {{domxref("HTMLElement.innerText")}} of the first paragraph to show the current `VisualViewport.offsetLeft` and `VisualViewport.offsetTop` values, and the `HTMLElement.innerText` of the second paragraph to show the current `Window.scrollX` and `Window.scrollY` values. After defining `updateText()`, we immediately invoke it so that the information box displays correctly on page load. ```js function updateText() { From f62c7d3dc426b66fc88e42909ad26509cc18cf89 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Tue, 27 Aug 2024 11:22:00 +0100 Subject: [PATCH 8/8] Last few fixes for wbamberg comments --- files/en-us/web/api/visual_viewport_api/index.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/files/en-us/web/api/visual_viewport_api/index.md b/files/en-us/web/api/visual_viewport_api/index.md index 0fca0b6a9c612ea..e3f17687b7aaaf0 100644 --- a/files/en-us/web/api/visual_viewport_api/index.md +++ b/files/en-us/web/api/visual_viewport_api/index.md @@ -34,7 +34,7 @@ The first two events allow you to position elements relative to the visual viewp ## Examples -Our [Visual viewport events](https://visual-viewport-events.glitch.me/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in supporting desktop and mobile browsers and try scrolling around the displayed grid of boxes. Also try pinch-zooming on the mobile browser. On `resize` and `scroll`, the information box is repositioned to keep the same position relative to the visual viewport, and the viewport and scroll information it shows is updated. Also, on `resize` and `scroll` we change the box color to indicate something is happening, changing it back on `scrollend`. +Our [Visual Viewport API](https://mdn.github.io/dom-examples/visual-viewport-api/) example provides a basic demonstration of how the different visual viewport features work, including the three event types. Load the page in supporting desktop and mobile browsers and try scrolling around the page and pinch-zooming. On `resize` and `scroll`, the information box is repositioned to keep the same position relative to the visual viewport, and the viewport and scroll information it shows is updated. Also, on `resize` and `scroll` we change the box color to indicate something is happening, changing it back on `scrollend`. You'll find that on desktop browsers the {{domxref("Window.scrollX")}} and {{domxref("Window.scrollY")}} values are updated as the window is scrolled — the visual viewport position does not change. On mobile browsers however, the {{domxref("VisualViewport.offsetLeft")}} and {{domxref("VisualViewport.offsetTop")}} values are generally updated — it is usually the visual viewport that changes rather than the window position. @@ -42,8 +42,7 @@ The example HTML can be seen below. The information box is represented by a {{ht ```html

- Try scrolling around on a desktop and a mobile browser to see how the reported - values change. Also try pinch/zooming on a mobile browser to see the effect. + Try scrolling around and pinch-zooming to see how the reported values change.

@@ -100,7 +99,7 @@ updateText(); Now we set event handler properties on both the visual viewport and the {{domxref("Window")}} object to run the key functions at the appropriate times on both mobile and desktop: - We set the handlers on `window` so that the information box position and contents will update on conventional window scrolling operations, for example when you scroll the page on a desktop browser. -- We set the handlers on `visualViewport` so that the information box position and contents will update on visual viewport scrolling/zooming operations, for example when you pinch-zoom and then scroll the page on a mobile browser. +- We set the handlers on `visualViewport` so that the information box position and contents will update on visual viewport scrolling/zooming operations, for example when you scroll and pinch-zoom the page on a mobile browser. ```js visualViewport.onresize = scrollUpdater;