Skip to content

Commit 2d79671

Browse files
committed
Touch Device: Trigger popup on fast scroll-up
1 parent ef410ef commit 2d79671

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Your content will get displayed via [Slots](https://vuejs.org/guide/components/s
6060
| **scrollPercentage** | 0 | Number | false |
6161
| **navigateBeforeShowSeconds** | 0 | Number | false |
6262
| **mouseOutEnabled** | true | Boolean | false |
63+
| **touchDeviceSensitivity** | 5 | Number | false |
6364
| **showByDefault** | false | Boolean | false |
6465
| **showCloseBtn** | true | Boolean | false |
6566
| **color** | '#555' | String | false |
@@ -85,6 +86,12 @@ How many seconds the user has to navigate before the pop-up gets triggered.
8586
**Give 0 to disable.**
8687
**This one TRIGGERS the popup.**
8788

89+
- **touchDeviceSensitivity**
90+
On touch devices where there is no mouseleave event, the popup will get triggered on fast (touch)scroll up.
91+
The larger the number you will give, the more sesitive will be the pop-up on touch devices.
92+
**Give 0 to disable.**
93+
**This one TRIGGERS the popup on touch devices.**
94+
8895
- **mouseOutEnabled**
8996
If false. Mouse out event will not trigger the pop-up. The user would have to reach navigateBeforeShowSeconds or scrollPercentage to get the popup.
9097
Ir true, well.. you know, the pop-up is set to get showed on user exit-intent

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-exit-intent",
3-
"version": "1.1.2",
3+
"version": "1.2.0",
44
"description": "Vue 3 pop-up that shows up when a user is leaving, or another threshold reached.",
55
"author": "nickap <dev.texng@simplelogin.com>",
66
"license": "MIT",

src/components/vueExitIntent.vue

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ const props = defineProps({
66
scrollPercentage: { type: Number, required: false, default: 0 },
77
/** TODO: Fix prop name. Maybe: 'waitSecondsAndShow' */
88
navigateBeforeShowSeconds: { type: Number, required: false, default: 0 },
9+
/** TODO: Fix prop name. Maybe: 'exitIntentEnabled' */
910
mouseOutEnabled: { type: Boolean, required: false, default: true },
11+
touchDeviceSensitivity: { type: Number, required: false, default: 5 },
1012
showByDefault: { type: Boolean, required: false, default: false },
1113
showCloseBtn: { type: Boolean, required: false, default: true },
1214
color: { type: String, required: false, default: '#555' },
@@ -21,10 +23,16 @@ const props = defineProps({
2123
2224
const show = ref(false);
2325
let scrollHandler = null;
26+
let touchDeviceExitIntentHandler = null;
2427
2528
onMounted(() => {
2629
if (props.mouseOutEnabled) {
27-
document.documentElement.addEventListener('mouseleave', handleMouseLeave);
30+
if (props.touchDeviceSensitivity && isTouchDevice()) {
31+
touchDeviceExitIntentHandler = showOnFastTouchScrollUp();
32+
window.addEventListener('scroll', touchDeviceExitIntentHandler);
33+
} else {
34+
document.documentElement.addEventListener('mouseleave', handleMouseLeave);
35+
}
2836
}
2937
if (props.navigateBeforeShowSeconds) {
3038
if (isAllowedToShow() && isLocalStorageExpired()) {
@@ -45,6 +53,7 @@ onMounted(() => {
4553
onUnmounted(() => {
4654
document.documentElement.removeEventListener('mouseleave', handleMouseLeave);
4755
window.removeEventListener('scroll', scrollHandler);
56+
window.removeEventListener('scroll', touchDeviceExitIntentHandler);
4857
});
4958
5059
const handleMouseLeave = () => {
@@ -103,6 +112,50 @@ const isLocalStorageExpired = () => {
103112
}
104113
};
105114
115+
const isTouchDevice = () => {
116+
if ('maxTouchPoints' in navigator) {
117+
return navigator.maxTouchPoints > 0;
118+
} else if ('msMaxTouchPoints' in navigator) {
119+
return navigator.msMaxTouchPoints > 0;
120+
} else {
121+
const mQ = window.matchMedia && matchMedia('(pointer:coarse)');
122+
if (mQ && mQ.media === '(pointer:coarse)') {
123+
return !!mQ.matches;
124+
} else if ('orientation' in window) {
125+
/* Deprecated, used as a fallback */
126+
return true;
127+
} else {
128+
/* Fallback to user agent sniffing*/
129+
const UA = navigator.userAgent;
130+
return (
131+
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
132+
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
133+
);
134+
}
135+
}
136+
};
137+
138+
const showOnFastTouchScrollUp = () => {
139+
let isScrolling, startPos, finalPos, destUpwards;
140+
let i = 0;
141+
return () => {
142+
i++;
143+
if (i == 1) startPos = window.scrollY;
144+
/* Clear our timeout throughout the scroll */
145+
window.clearTimeout(isScrolling);
146+
isScrolling = setTimeout(() => {
147+
finalPos = window.scrollY;
148+
destUpwards = startPos - finalPos;
149+
if (destUpwards > 5000 / props.touchDeviceSensitivity) {
150+
if (isAllowedToShow() && isLocalStorageExpired()) {
151+
showModal();
152+
}
153+
}
154+
i = 0;
155+
}, 50);
156+
};
157+
};
158+
106159
const closeModal = () => {
107160
show.value = false;
108161
document.body.style.overflowY = 'auto';

0 commit comments

Comments
 (0)