美文网首页
css滚动继承问题

css滚动继承问题

作者: 科科Cole | 来源:发表于2021-11-01 17:45 被阅读0次

css中有一类滚动继承问题。假如现在有一个淘宝商品页,商品列表可以滚动,列表上面有一个筛选按钮,点击可弹出一个筛选列表:


假如图中黄色框选区域可以滚动,当滚动到底部边缘再继续滚动时,会发现商品列表竟然开始滚动了,怎么会是呢?
首先可以先看一下这篇文章:CSS overscroll-behavior让滚动嵌套时父滚动不触发,同时里面有作者对这一问题的处理方法。这个问题主要是在写移动端页面的时候会遇到,到目前为止,safari浏览器还是不支持overscroll-behavior属性,所以没法通过设置这个属性解决。我目前的解决办法是在vant的源码中找到的。vant里有一个下拉菜单组件:
DropdownMenu
这里面也涉及了滚动继承问题,vant做了相应处理。
以下代码为Vue2.x下的代码:

目录结构:

- views
-- demo.vue
- mixins
-- touch.js
-- overscroll
--- index.js
- utils
-- dom
--- event.js
--- scroll.js

scroll.js文件内容:

// get nearest scroll element
// https://github.com/youzan/vant/issues/3823
/**
 * 获取最近触发滚动事件的元素
 * refer to https://github.com/youzan/vant/blob/v2.12.15/src/utils/dom/scroll.ts
 * @param {Element} el 当前元素
 * @param {Element | Window} root 根元素
 * @returns {Element | Window}
 */
const overflowScrollReg = /scroll|auto/i;
export function getScroller(el, root = window) {
  let node = el;

  while (
    node &&
    node.tagName !== "HTML" &&
    node.tagName !== "BODY" &&
    node.nodeType === 1 &&
    node !== root
  ) {
    const { overflowY } = window.getComputedStyle(node);
    if (overflowScrollReg.test(overflowY)) {
      return node;
    }
    node = node.parentNode;
  }

  return root;
}

event.js文件内容

/**
 * 阻止事件冒泡
 * @param {Event} event
 * @returns {void}
 */
export function stopPropagation(event) {
  event.stopPropagation();
}

/**
 * 阻止默认事件
 * refer to https://github.com/youzan/vant/blob/v2.12.15/src/utils/dom/event.ts
 * @param {Event} event
 * @param {boolean} isStopPropagation
 * @returns {void}
 */
export function preventDefault(event, isStopPropagation) {
  if (typeof event.cancelable !== "boolean" || event.cancelable) {
    event.preventDefault();
  }
  if (isStopPropagation) {
    stopPropagation(event);
  }
}

touch.js文件内容:

/**
 * @description touch事件混入
 * refer to https://github.com/youzan/vant/blob/v2.12.15/src/mixins/touch.js
 */

const MIN_DISTANCE = 10;

function getDirection(x, y) {
  if (x > y && x > MIN_DISTANCE) {
    return "horizontal";
  }

  if (y > x && y > MIN_DISTANCE) {
    return "vertical";
  }

  return "";
}

export const touch = {
  data() {
    return {
      direction: "", // 移动方向 horizontal或vertical
      startX: 0, // touchstart X值
      startY: 0, // touchstart Y值
      deltaX: 0, // touchmove X轴差值 带符号
      deltaY: 0, // touchmove Y轴差值 带符号
      offsetX: 0, // touchmove X轴偏移量
      offsetY: 0 // touchmove Y轴偏移量
    };
  },
  methods: {
    touchStart(event) {
      this.resetTouchStatus();
      this.startX = event.touches[0].clientX;
      this.startY = event.touches[0].clientY;
    },
    touchMove(event) {
      const touch = event.touches[0];
      // Fix: Safari back will set clientX to negative number
      this.deltaX = touch.clientX < 0 ? 0 : touch.clientX - this.startX;
      this.deltaY = touch.clientY - this.startY;
      this.offsetX = Math.abs(this.deltaX);
      this.offsetY = Math.abs(this.deltaY);
      this.direction =
        this.direction || getDirection(this.offsetX, this.offsetY);
    },
    resetTouchStatus() {
      this.direction = "";
      this.deltaX = 0;
      this.deltaY = 0;
      this.offsetX = 0;
      this.offsetY = 0;
      this.startX = 0;
      this.startY = 0;
    }
  }
};

overscroll/index.js文件内容:

/**
 * @description 阻止滚动继承,类似overscroll-behavior: contain
 * refer to https://github.com/youzan/vant/blob/v2.12.15/src/mixins/popup/index.js
 */

import { getScroller } from "@/utils/dom/scroll";
import { preventDefault } from "@/utils/dom/event";
import { touch } from "../touch";

export const OverscrollMixin = {
  mixins: [touch],
  methods: {
    onTouchMove(event) {
      this.touchMove(event);
      // '10'-下拉 '01'-上拉
      const direction = this.deltaY > 0 ? "10" : "01";
      const el = getScroller(event.target, this.$el);
      const { scrollHeight, offsetHeight, scrollTop } = el;
      let status = "11";
      if (scrollTop === 0) {
        // 处于顶部
        // offsetHeight>=scrollHeight 说明元素不可滚动
        // '00'-元素不可滚动 '01'-元素可滚动
        status = offsetHeight >= scrollHeight ? "00" : "01";
      } else if (scrollTop + offsetHeight >= scrollHeight - 1) {
        // 由于offsetHeight为小数,因此可能会出现scrollTop + offsetHeight略小于scrollHeight的情况,因此-1
        // 处于底部
        status = "10";
      }
      if (
        status !== "11" &&
        this.direction === "vertical" &&
        !(parseInt(status, 2) & parseInt(direction, 2))
      ) {
        // 元素不处于顶部或底部
        // 且滚动方向为垂直
        // 且向上拉拉到底部了或向下拉拉到顶部了
        preventDefault(event, true);
      }
    },
  },
};

最后,在demo.vue中引入overscroll/index.js作为混入:

import { OverscrollMixin } from "@/mixins/overscroll";

export default {
  mixins: [OverscrollMixin]
}

在你不想要滚动继承的元素上添加事件绑定函数,这个元素本身需要是可滚动的,即overflow-y需要是scroll或auto。假设在demo.vue中可滚动元素类名为"content":

<template>
  <div class="page_container">
    ...
    <div ref="content" class="content">
    </div>
    ...
  </div>
</template>

需要在mounted生命周期中为content绑定touchstart和touchmove事件:

export default {
  ...
  mounted() {
    let content = this.$refs.content;
    content.addEventListener("touchstart", this.touchStart, {
      passive: false,
    });
    content.addEventListener("touchmove", this.onTouchMove, {
      passive: false,
    });
  },
  ...
}

touchStart和onTouchMove方法都是在OverscrollMixin里混入来的,当然OverscrollMixin本身也混入了其他混入。以上代码如果用vue3的composition api来写会更加清晰。
如果你的页面加了keep-alive,可以将上面mounted里的方法写在activated里,同时在deactiveted里也要移除一下事件监听。


这个方法通过监听touchstart和touchmove方法,记录手指划过的方向,同时寻找当前点击元素最近的父代可滚动元素,计算是否滚动到顶部或底部,如果滚动到顶部或底部则阻止默认事件,防止滚动继承的发生。
这里的代码其实还能再写得好些,不过先这样吧。

相关文章

  • css滚动继承问题

    css中有一类滚动继承问题。假如现在有一个淘宝商品页,商品列表可以滚动,列表上面有一个筛选按钮,点击可弹出一个筛选...

  • 前端开发遇到的问题

    CSS 1、页面平滑滚动 问题: 需要在滚动到顶部的时候有缓慢滚动的动画效果 方案: scroll-behavio...

  • css滚动

    布局:水平滚动,垂直滚动 垂直滚动: 水平滚动: css样式:

  • css滚动

    布局:水平滚动,垂直滚动 垂直滚动: 水平滚动: css样式:

  • 导航的布局:水平滚动,垂直滚动

    垂直滚动: 水平滚动: css样式:

  • 纯 CSS 滚动进度条效果,太秀了

    问题先行,如何使用 CSS 实现下述滚动条效果? 就是顶部黄色的滚动进度条,随着页面的滚动进度而变化长短。 在继续...

  • 不可思议的纯 CSS 滚动进度条效果

    问题先行,如何使用 CSS 实现下述滚动条效果? 就是顶部黄色的滚动进度条,随着页面的滚动进度而变化长短。 在继续...

  • 不可思议的纯 CSS 滚动进度条效果

    问题先行,如何使用 CSS 实现下述滚动条效果? 就是顶部黄色的滚动进度条,随着页面的滚动进度而变化长短。 在继续...

  • CSS设置滚动条样式

    CSS设置滚动条样式 一:webkit下面的CSS设置滚动条 ::-webkit-scrollbar 滚动条整体部...

  • 2018-10-19

    学习重点 CSS定位 计时器及无限滚动 arrow,buttons的设置及效果 遗留问题 无限滚动对应代码(未能彻...

网友评论

      本文标题:css滚动继承问题

      本文链接:https://www.haomeiwen.com/subject/yarzaltx.html