ios滑动穿透问题

3,280 阅读2分钟

问题表象: fix定位的浮层,在浮层上存在滚动内容,触发滚动,在ios上会偶尔触发底层的滚动。

方法1、

给body加overflow:hidden,同时给body的height为100vh。

问题,会让body 内的滚动内容还原到初始位置。

可以在遮罩弹起前记录当时位置,在遮罩关闭后还原。

问题,内容会闪,如果部分遮罩的背景是透明的话,可以明显的看到后面的位置变了。

方法2、

在遮罩弹起后,将所有的滚动事件禁止掉,同时在需要滚动的元素添加特殊attribute,只允许事件对象中带有特定的attribute的元素触发touchmove。

问题,安卓上良好解决,ios上,你在所允许的元素上触发滚动,他有时会触发该遮罩层的滚动,有时会穿透到底层,引起底层的滚动,看起来很恶心,难搞啊。

方法3、

解决方法,禁止所有滚动,手动实现滚动。

封装如下组件,具体方法如下。

思想:

  • didNount禁止所有滚动。

  • unmount取消滚动禁止。

  • touchStart记录该滚动元素初始高度,初始的touch位置。

  • findParent递归向上查询滚动是否在允许滚动的区域内,确认才允许其滚动。

缺点如下:

  • 代价较大,本应该用css来实现,但却用一堆js来实现。
  • 这种滚动方式也丧失了ios中原有的橡皮筋效果。
  • 没有实现多层滚动的逻辑。
import React, {Component} from 'react';
/*
   接收props id
   解决ios浮层上滚动穿透问题
   禁止所有滚动 手动实现滚动
 */
export default class ScrollAllow extends Component {
    constructor(props) {
        super(props);
        this.touchStart = 0; // touchstart的初始高度
        this.initStart = 0; // 滚动元素的初始高度
    }
    // 创建后禁止默认的滑动事件
    componentDidMount(){
        window.addEventListener('touchmove', this.scrollFun, {passive: false});
        window.addEventListener('touchstart', this.touchstart, {passive: false});
    }
    // moveStart 记录初始滑动的位置
    touchstart = event => {
        let {id} = this.props;
        this.touchStart = event.targetTouches[0].clientY;
        var dom = document.getElementById(id);
        if(dom) {
            this.initStart = dom.scrollTop;
        }
    };
    // 判断所滑动元素是否在该id下 如果不在则不进行滚动
    findParent = (dom) => {
        let {id} = this.props;
        let nodeData = dom.getAttribute('id') === id;
        if (nodeData) {
            return dom;
        } else if (dom.nodeName === 'BODY') {
            return false;
        }
        let parentNode = dom.parentNode;
        return this.findParent(parentNode);
    };
    // 滚动事件记录滚动高度 并手动执行滚动
    scrollFun = (event) => {
        let {id} = this.props;
        var dom = document.getElementById(id);
        const clickedDom = event.target;
        // 判断该滑动事件是否在此id下
        let isThisIdDom = this.findParent(clickedDom);
        if(isThisIdDom){
            dom.scrollTop = (this.touchStart - event.touches[0].clientY) + this.initStart;
        }
        return event.preventDefault();
    };
    // 卸载后恢复滚动事件
    componentWillUnmount(){
        window.removeEventListener('touchmove', this.scrollFun);
        window.removeEventListener('touchstart', this.scrollFun);
    }
    render() {
        let {id} = this.props;
        return (
            <div id={id}>
                {this.props.children}
            </div>
        );
    }
}