import { useState, useEffect } from "react";
interface Options {
height?: number;
tapSelector?: string; // 多个用逗号隔开
yScrollSelector?: string; // 纵向滚动条,多个用逗号隔开
xScrollSelector?: string; // 横向滚动条,多个用逗号隔开
}
export default function useVisualViewport(options?: Options) {
const [height, setHeight] = useState(options.height || 0);
const [debugInfo, setDebugInfo] = useState([]);
useEffect(() => {
let pendingUpdate = false;
function viewportHandler(event) {
if (pendingUpdate) return;
pendingUpdate = true;
requestAnimationFrame(() => {
pendingUpdate = false;
setHeight(window.visualViewport.offsetTop);
});
}
window.visualViewport.addEventListener("scroll", viewportHandler);
window.visualViewport.addEventListener("resize", viewportHandler);
var startY = 0;
var minScrollTop = -Infinity;
function handleTouchstart(e) {
// setDebugInfo(['start', height]);
// console.log('handleTouchstart', e)
const tap = Array.from(document.querySelectorAll(options.tapSelector));
const hasTap = tap.some((item) => item.contains(e.target));
const yScroll = Array.from(
document.querySelectorAll(options.yScrollSelector)
);
const yScrollContainer = yScroll.find((item) => item.contains(e.target));
const xScroll = Array.from(
document.querySelectorAll(options.xScrollSelector)
)
const xScrollContainer = xScroll.find((item) => item.contains(e.target));
if (hasTap || xScrollContainer) {
// 不做任何事情
} else if (yScrollContainer) {
startY = e.changedTouches[0].clientY;
minScrollTop =
yScrollContainer.clientHeight - yScrollContainer.scrollHeight + 1;
// console.log('startY', startY, minScrollTop)
} else {
e.preventDefault();
}
}
function handleTouchmove(e) {
// console.log('handleTouchmove', e)
const yScroll = Array.from(
document.querySelectorAll(options.yScrollSelector)
);
const yScrollContainer = yScroll.find((item) => item.contains(e.target));
const clientY = e.changedTouches[0].clientY;
const end = clientY - startY;
const xScroll = Array.from(
document.querySelectorAll(options.xScrollSelector)
)
const xScrollContainer = xScroll.find((item) => item.contains(e.target));
e.stopPropagation();
if (xScrollContainer) {
// 不做任何事情
} else if (yScrollContainer) {
var scrollTop = yScrollContainer.scrollTop;
var clientHeight = yScrollContainer.clientHeight;
var scrollHeight = yScrollContainer.scrollHeight;
const isTextarea = yScrollContainer.tagName === "TEXTAREA";
// setDebugInfo(['move', scrollTop, clientHeight, scrollHeight, minScrollTop]);
// 下拉
if (end > 0) {
// 到顶了
if (isTextarea ? scrollTop <= 0 : scrollTop < minScrollTop) {
// console.log('到顶了')
e.preventDefault();
}
} else if (end < 0) {
// 到底了
if (
isTextarea ? scrollTop >= Math.abs(minScrollTop) : scrollTop === 0
) {
// console.log('到底了')
e.preventDefault();
}
}
} else {
e.preventDefault();
}
}
window.addEventListener("touchstart", handleTouchstart, { passive: false });
window.addEventListener("touchmove", handleTouchmove, { passive: false });
return () => {
window.visualViewport.removeEventListener("scroll", viewportHandler);
window.visualViewport.removeEventListener("resize", viewportHandler);
window.removeEventListener("touchstart", handleTouchstart);
window.removeEventListener("touchmove", handleTouchmove);
};
}, []);
return [height, debugInfo];
}
调用方法
const [height] = useVisualViewport({
height: 0,
// 如果弹出键盘还需要操作可以添加以下属性
tapSelector: '.btn,.btn1', // 可点击区域, 多个用逗号分隔
yScrollSelector: '.y-scroll,.y-scroll1', // 纵向滚动条, 多个用逗号分隔
xScrollSelector: '.x-scroll,.x-scroll1' // 横向滚动条, 多个用逗号分隔
})
return <>
<div style={{height: height + 'px'}} />
<div className='container'>
</div>
</>
body {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
.container {
flex: 1;
min-height: 0;
}