Skip to content

Commit

Permalink
Merge pull request #37 from viivue/staging-fix-jumping-scrollbar-on-open
Browse files Browse the repository at this point in the history
Fix jumping scrollbar on open
  • Loading branch information
phucbm authored Oct 19, 2024
2 parents e556229 + 0d16168 commit 1e947a8
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 46 deletions.
15 changes: 15 additions & 0 deletions dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@
padding:15px;
}
}


/* custom scrollbar */
/* Works on Chrome, Edge, and Safari */
body::-webkit-scrollbar {
width:8px;
}

body::-webkit-scrollbar-track {
background:#ccc;
}

body::-webkit-scrollbar-thumb {
background-color:#000;
}
</style>
</head>
<body class="markdown-body">
Expand Down
52 changes: 52 additions & 0 deletions dev/md/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## FAQ

<details>
<summary>1. The page got jumping when the popup is shown. How to fix it?</summary>

You need to understand how `preventScroll` works in Easy Popup.
When you enable the `preventScroll` option:

1. Easy Popup adds an `ep-prevent-scroll` class to the `<body>`.
- This sets `overflow: hidden` to prevent page scrolling.
2. Hiding the scrollbar can cause the page to shift right.
3. To prevent shifting, Easy Popup adds `padding-right` to the `<body>`.
- The padding is equal to the scrollbar width.
4. The scrollbar width is auto-calculated by JavaScript.
- Note: This may not be 100% accurate across all browsers/devices.

Easy Popup has a built-in function to calculate the scrollbar width automatically depending on the browser/device.
However, this may not work as expected in some cases.

For optimal results:

- Set the scrollbar width via CSS.
- Use the same value for the `scrollbarWidth` option in Easy Popup.

```css
/* custom scrollbar */
/* Works on Chrome, Edge, and Safari */
body::-webkit-scrollbar {
width:8px;
}

body::-webkit-scrollbar-track {
background:#ccc;
}

body::-webkit-scrollbar-thumb {
background-color:#000;
}
```

Easy Popup options

```json
{
"preventScroll": true,
"scrollbarWidth": 8
}
```

Note: On touch screen, the scrollbar width is set to `0` automatically.

</details>
35 changes: 18 additions & 17 deletions dev/md/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

### Options

| Attribute | Type | Default | Description |
|------------------------|------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------|
| `id` | string | Auto-generated ID | Set an ID for this popup |
| `theme` | string | `default` | Other layouts: `right-side` |
| `hasMobileLayout` | boolean | `false` | Enable mobile layout (`theme` options will not work when mobile layout is active) |
| `mobileBreakpoint` | number | `768` | Activate mobile layout when the screen size is <=768px |
| `closeButtonInnerText` | string | `svg/icon` | Custom `innerText` of the close button |
| `triggerSelector` | CSS selector | `""` | Click on this trigger will also toggle the popup |
| `outerClass` | string | `""` | Extra classes to popup outer `.easy-popup`, use white space for multiple classes, e.g. "class1 class2" |
| `activeHtmlClass` | string | `""` | Extra class to `<html>` when a popup opens |
| `keyboard` | boolean | `true` | Close popup by pressing ESC key |
| `clickOutsideToClose` | boolean | `true` | Click on empty outside an opening open will close the popup |
| `preventScroll` | boolean | `true` | Prevent page scroll when popup is open |
| `autoShow` | boolean or number (ms) | `false` | `true` to show the popup right after page loaded, set number for delay, e.g. 1000 for 1000ms after init |
| `cookie` | string or number | `undefined` | Requires `autoShow:true`. Set expiration for a popup. Use [PiaJs `expires`](https://github.com/phucbm/pia#set-expires). |
| `showingTimes` | number | `1` | Requires `cookie`. Show n times before expiration day, only works with cookie |
| `cookieName` | string | `""` | Requires `cookie`. Name of the cookie, change name will also lose access to the previous cookie => treat as a new cookie |
| Attribute | Type | Default | Description |
|------------------------|------------------------|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | Auto-generated ID | Set an ID for this popup |
| `theme` | string | `default` | Other layouts: `right-side` |
| `hasMobileLayout` | boolean | `false` | Enable mobile layout (`theme` options will not work when mobile layout is active) |
| `mobileBreakpoint` | number | `768` | Activate mobile layout when the screen size is <=768px |
| `closeButtonInnerText` | string | `svg/icon` | Custom `innerText` of the close button |
| `triggerSelector` | CSS selector | `""` | Click on this trigger will also toggle the popup |
| `outerClass` | string | `""` | Extra classes to popup outer `.easy-popup`, use white space for multiple classes, e.g. "class1 class2" |
| `activeHtmlClass` | string | `""` | Extra class to `<html>` when a popup opens |
| `keyboard` | boolean | `true` | Close popup by pressing ESC key |
| `clickOutsideToClose` | boolean | `true` | Click on empty outside an opening open will close the popup |
| `preventScroll` | boolean | `true` | Prevent page scroll when popup is open |
| `scrollbarWidth` | number | `undefined` | You mostly don't need this unless the page scroller is not the body tag. Set the scrollbar width manually to avoid page jumping when open a popup, only works for `preventScroll:true`. |
| `autoShow` | boolean or number (ms) | `false` | `true` to show the popup right after page loaded, set number for delay, e.g. 1000 for 1000ms after init |
| `cookie` | string or number | `undefined` | Requires `autoShow:true`. Set expiration for a popup. Use [PiaJs `expires`](https://github.com/phucbm/pia#set-expires). |
| `showingTimes` | number | `1` | Requires `cookie`. Show n times before expiration day, only works with cookie |
| `cookieName` | string | `""` | Requires `cookie`. Name of the cookie, change name will also lose access to the previous cookie => treat as a new cookie |

#### Deprecated options

Expand Down
2 changes: 2 additions & 0 deletions dev/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import mdOptions from "./md/options.md";
import mdEvents from "./md/events.md";
import mdEnd from "./md/end.md";
import mdBegin from "./md/begin.md";
import mdFaq from "./md/faq.md";
import {testCookie} from "./js/test-cookie";
import {testEvents} from "./js/test-events";
import {testMethods} from "./js/test-methods";
Expand Down Expand Up @@ -41,6 +42,7 @@ testCookie(root);
root.insertAdjacentHTML('beforeend', mdOptions);
testEvents(root);
testMethods(root);
root.insertAdjacentHTML('beforeend', mdFaq);
root.insertAdjacentHTML('beforeend', mdEnd);

// code highlight
Expand Down
4 changes: 2 additions & 2 deletions src/_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {CLASSES, ATTRS, DEFAULTS} from "./configs"
import {EventsManager, getOptionsFromAttribute} from '@phucbm/os-util';
import {uniqueId} from "./utils";
import LenisEasyPopup from "./lenis-easy-popup";
import {getScrollbarWidth} from "./helpers";
import {generateHTML} from "./html";
import {getScrollbarWidth} from "./utils/getScrollbarWidth";

/**
* Private class
Expand Down Expand Up @@ -125,7 +125,7 @@ class Popup{
}else{
// prevent via CSS
this.root.classList.add(CLASSES.preventScroll);
this.root.style.setProperty('--ep-scroll-bar-w', `${getScrollbarWidth()}px`);
this.root.style.setProperty('--ep-scroll-bar-w', `${getScrollbarWidth(this)}px`);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ export const DEFAULTS = {
cookieName: '', // name of the cookie, change name will also lose access to the previous cookie => treat as a new cookie

preventScroll: true, // prevent page scroll when popup is open
scrollbarWidth: undefined, // px, set the scrollbar width manually to avoid page jumping when open a popup, only works for preventScroll:true
}
export const CLOSE_SVG = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>';
27 changes: 0 additions & 27 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,4 @@ export function wrapElement(innerEl, outerEl = document.createElement('div')){
innerEl.parentNode.insertBefore(outerEl, innerEl);
outerEl.appendChild(innerEl);
return outerEl;
}


/**
* Get scrollbar width
* https://stackoverflow.com/a/986977/6453822
* @returns {number}
*/
export function getScrollbarWidth(){
// Creating invisible container
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll'; // forcing scrollbar to appear
outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps
document.body.appendChild(outer);

// Creating inner element and placing it in the container
const inner = document.createElement('div');
outer.appendChild(inner);

// Calculating difference between container's full width and the child width
const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);

// Removing temporary elements from the DOM
outer.parentNode.removeChild(outer);

return scrollbarWidth;
}
76 changes: 76 additions & 0 deletions src/utils/getScrollbarWidth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {isMobile} from "./isMobile";

/**
* To prevent page jump when popup is open, we add a padding-right to the body, this is the width of the scrollbar.
* Scrollbar width is different between browsers and OS, so we need to calculate it.
* But sometimes, the scrollbar width can be changed by using ::-webkit-scrollbar CSS, so the calculation is not 100% accurate.
* So we allow the user to set the scrollbar width manually via the `scrollbarWidth` option.
*/
export function getScrollbarWidth(context){
if(isMobile()){
// console.log('EasyPopup: Mobile or touch device detected, no need to calculate scrollbar width');
return 0;
}

const {scrollbarWidth} = context.options;

if(scrollbarWidth === undefined){
return getDetectedScrollbarWidth();
}

if(typeof scrollbarWidth === 'number'){
return scrollbarWidth;
}

console.warn('EasyPopup: `scrollbarWidth` must be a number, fallback to auto-detect');
return getDetectedScrollbarWidth();
}


/**
* Get scrollbar width
* Not 100% accurate, but it's the best way to get the scrollbar width
* https://stackoverflow.com/a/986977/6453822
* @returns {number}
*/
function getDetectedScrollbarWidth(outerElement = document.body){
// Use provided element or create a new div
const outer = outerElement || document.createElement('div');

// Store original styles
const originalStyles = {
visibility: outer.style.visibility,
overflow: outer.style.overflow,
msOverflowStyle: outer.style.msOverflowStyle
};

// Apply necessary styles
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll'; // forcing scrollbar to appear
outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps

if(!outerElement){
document.body.appendChild(outer);
}

// Creating inner element and placing it in the container
const inner = document.createElement('div');
outer.appendChild(inner);

// Calculating difference between container's full width and the child width
const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);

// Cleaning up
outer.removeChild(inner);

if(!outerElement){
outer.parentNode.removeChild(outer);
}else{
// Restore original styles
outer.style.visibility = originalStyles.visibility;
outer.style.overflow = originalStyles.overflow;
outer.style.msOverflowStyle = originalStyles.msOverflowStyle;
}

return scrollbarWidth;
}
8 changes: 8 additions & 0 deletions src/utils/isMobile.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1e947a8

Please sign in to comment.