溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

vue中pc移動滾動穿透問題如何解決

發(fā)布時(shí)間:2022-07-27 17:21:38 來源:億速云 閱讀:352 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“vue中pc移動滾動穿透問題如何解決”,在日常操作中,相信很多人在vue中pc移動滾動穿透問題如何解決問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”vue中pc移動滾動穿透問題如何解決”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

vue pc移動滾動穿透問題

上層無滾動(很簡單直接@touchmove.prevent)

<div @touchmove.prevent>
我是里面的內(nèi)容
</div>

上層有滾動

如果上層需要滾動的話,那么固定的時(shí)候先獲取 body 的滑動距離,然后用 fixed 固定,用 top 模擬滾動距離;不固定的時(shí)候用獲取 top 的值,然后讓 body 滾動到之前的地方即可。

示例如下:

    watch:{
        statusShow(val){
            if(val) {
                this.lockBody();
            } else {
                this.resetBody();
            }
        },
        calendarShow(val){
            if(val) {
                this.lockBody();
            } else {
                this.resetBody();
            }
        }
    },
 
    methods: {
        lockBody() {
            const { body } = document;
            const scrollTop = document.body.scrollTop ||                                 
            document.documentElement.scrollTop;
            body.style.position = 'fixed';
            body.style.width = '100%';
            body.style.top = `-${scrollTop}px`;
        },
        resetBody() {
            const { body } = document;
            const { top } = body.style;
            body.style.position = '';
            body.style.width = '';
            body.style.top = '';
            document.body.scrollTop = -parseInt(top, 10);
            document.documentElement.scrollTop = -parseInt(top, 10);
        },
}

body是DOM對象里的body子節(jié)點(diǎn),即 標(biāo)簽;

documentElement 是整個(gè)節(jié)點(diǎn)樹的根節(jié)點(diǎn)root,即 標(biāo)簽;

不同瀏覽器中,有的能識別document.body.scrollTop,有的能識別document.documentElement.scrollTop,有兼容性問題需要解決。

vue中pc移動滾動穿透問題如何解決

滑動穿透終極解決方案

問題描述

滑動穿透:浮層上的觸控會導(dǎo)致底層元素滑動。

問題探究

1、給body加overflow:hidden,pc端可以鎖scroll,移動端無效

pc端可以直接overflow:hidden解決

2、給body加overflow:hidden及絕對定位,背景會定位到頂部,如果是單屏頁面可以,長頁面不適用

如果彈出浮層時(shí)背景本來就沒有滾動距離,可以overflow:hidden加絕對定位解決

3、禁用touchmove事件,如@touchmove.prevent,對于彈層不需要的滑動的元素來說非常好用,因?yàn)閟croll是touchmove觸發(fā)的,直接禁用就不會滑動穿透了,其實(shí)是直接就沒有系統(tǒng)滑動事件了。但是顯然不適合彈層需要滑動的情況

如果彈層時(shí)不需要滾動的,可以直接禁用touchmove就可以了

4、專門解決滑動穿透的第三方,存在巨大的兼容性問題。比如tua-body-scroll-lock,android可以完美解決,ios整個(gè)屏幕都不能滑動了。高星的body-scroll-lock據(jù)說android全掛,就沒有試了。

第三方有兼容性問題,可以自己判斷ua選用

5、終極解決方案:vant的popup

合理完美的解決方案,不存在兼容問題,適用于任何情況的popup。如果你不想為了鎖背景引入一個(gè)根本用不到的庫,可以一起來研究下popup的實(shí)現(xiàn)原理。

原理探究

如果不想看源碼想直接知道結(jié)論的話可以看這里:

因?yàn)槌R姇瑒哟┩傅膱鼍岸际牵?/strong>

  • 子元素本來就不可滾動,在子元素上滑動引起背景滾動,

  • 子元素可以滾動,但已經(jīng)滾動到頂部或者底部,繼續(xù)滑動的話就會滑動穿透

所以如果子元素本身不可滾動,或者子元素氪滾動,但已經(jīng)滾動到頂部或者底部時(shí)直接對touchmove進(jìn)行默認(rèn)事件阻止就可以阻止滑動穿透了。因?yàn)閟croll事件是通過touchmove觸發(fā)的,禁止掉就不會觸發(fā)系統(tǒng)的scroll事件了。這樣就可以完美解決可滾動元素可以滾動但其背景在滑動時(shí)不為所動的效果了。

如果你想看看popup到底時(shí)如何做的可以來看看下面的源碼:

源碼分析:

src/popup/index.js文件中主要是參數(shù)及界面顯示的處理。

// src/popup/index.js
import { createNamespace, isDef } from '../utils';
import { PopupMixin } from '../mixins/popup';
import Icon from '../icon';
const [createComponent, bem] = createNamespace('popup');
export default createComponent({
  // 穿透處理的代碼在這里混入
  mixins: [PopupMixin],
  props: {
    round: Boolean,
    duration: Number,
    closeable: Boolean,
    transition: String,
    safeAreaInsetBottom: Boolean,
    closeIcon: {
      type: String,
      default: 'cross'
    },
    closeIconPosition: {
      type: String,
      default: 'top-right'
    },
    position: {
      type: String,
      default: 'center'
    },
    overlay: {
      type: Boolean,
      default: true
    },
    closeOnClickOverlay: {
      type: Boolean,
      default: true
    }
  },
  beforeCreate() {
    const createEmitter = eventName => event => this.$emit(eventName, event);
    this.onClick = createEmitter('click');
    this.onOpened = createEmitter('opened');
    this.onClosed = createEmitter('closed');
  },
  render() {
    if (!this.shouldRender) {
      return;
    }
    const { round, position, duration } = this;
    const transitionName =
      this.transition ||
      (position === 'center' ? 'van-fade' : `van-popup-slide-${position}`);
    const style = {};
    if (isDef(duration)) {
      style.transitionDuration = `${duration}s`;
    }
    return (
      <transition
        name={transitionName}
        onAfterEnter={this.onOpened}
        onAfterLeave={this.onClosed}
      >
        <div
          vShow={this.value}
          style={style}
          class={bem({
            round,
            [position]: position,
            'safe-area-inset-bottom': this.safeAreaInsetBottom
          })}
          onClick={this.onClick}
        >
          {this.slots()}
          {this.closeable && (
            <Icon
              role="button"
              tabindex="0"
              name={this.closeIcon}
              class={bem('close-icon', this.closeIconPosition)}
              onClick={this.close}
            />
          )}
        </div>
      </transition>
    );
  }
});

根據(jù)mixins混入,可以看到核心部分應(yīng)該在src/mixins/popup中,在這里針對lockscroll做出了兩種處理,綁定touchmove及touchstart并綁定class:van-overflow-hidden

// src/mixins/popup/index.js
import { context } from './context';
import { TouchMixin } from '../touch';
import { PortalMixin } from '../portal';
import { on, off, preventDefault } from '../../utils/dom/event';
import { openOverlay, closeOverlay, updateOverlay } from './overlay';
import { getScrollEventTarget } from '../../utils/dom/scroll';
export const PopupMixin = {
  mixins: [
    TouchMixin,
    PortalMixin({
      afterPortal() {
        if (this.overlay) {
          updateOverlay();
        }
      }
    })
  ],
  props: {
    // whether to show popup
    value: Boolean,
    // whether to show overlay
    overlay: Boolean,
    // overlay custom style
    overlayStyle: Object,
    // overlay custom class name
    overlayClass: String,
    // whether to close popup when click overlay
    closeOnClickOverlay: Boolean,
    // z-index
    zIndex: [Number, String],
    // prevent body scroll
    lockScroll: {
      type: Boolean,
      default: true
    },
    // whether to lazy render
    lazyRender: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      inited: this.value
    };
  },
  computed: {
    shouldRender() {
      return this.inited || !this.lazyRender;
    }
  },
  watch: {
    value(val) {
      const type = val ? 'open' : 'close';
      this.inited = this.inited || this.value;
      this[type]();
      this.$emit(type);
    },
    overlay: 'renderOverlay'
  },
  mounted() {
    if (this.value) {
      this.open();
    }
  },
  /* istanbul ignore next */
  activated() {
    if (this.value) {
      this.open();
    }
  },
  beforeDestroy() {
    this.close();
    if (this.getContainer && this.$parent && this.$parent.$el) {
      this.$parent.$el.appendChild(this.$el);
    }
  },
  /* istanbul ignore next */
  deactivated() {
    this.close();
  },
  methods: {
    open() {
      /* istanbul ignore next */
      if (this.$isServer || this.opened) {
        return;
      }
      // cover default zIndex
      if (this.zIndex !== undefined) {
        context.zIndex = this.zIndex;
      }
      this.opened = true;
      this.renderOverlay();
      // 穿透處理的核心部分
      if (this.lockScroll) {
        // 給touchstart及touchmove上綁定代碼
        // 關(guān)于touchStart及ontouchmove的代碼在TouchMixin的引入中
        on(document, 'touchstart', this.touchStart);
        on(document, 'touchmove', this.onTouchMove);
        if (!context.lockCount) {
          document.body.classList.add('van-overflow-hidden');
        }
        context.lockCount++;
      }
    },
    close() {
      if (!this.opened) {
        return;
      }
      if (this.lockScroll) {
        context.lockCount--;
        off(document, 'touchstart', this.touchStart);
        off(document, 'touchmove', this.onTouchMove);
        if (!context.lockCount) {
          document.body.classList.remove('van-overflow-hidden');
        }
      }
      this.opened = false;
      closeOverlay(this);
      this.$emit('input', false);
    },
    onTouchMove(event) {
      // 這個(gè)方法是touch文件中引入得,一會會看到
      // 主要計(jì)算滑動得方向及距離
      this.touchMove(event);
      // 方向計(jì)算
      const direction = this.deltaY > 0 ? '10' : '01';
      // 獲取滾動目標(biāo)對象
      const el = getScrollEventTarget(event.target, this.$el);
      // 滾動元素相關(guān)屬性賦值
      const { scrollHeight, offsetHeight, scrollTop } = el;
      let status = '11';
      /* istanbul ignore next */
      if (scrollTop === 0) {
        // 沒有滾動的情況下,判定是否有滾動條
        status = offsetHeight >= scrollHeight ? '00' : '01';
      } else if (scrollTop + offsetHeight >= scrollHeight) {
        // 有滾動距離且滾動到底部
        status = '10';
      }
      /* istanbul ignore next */
      if (
        status !== '11' &&
        this.direction === 'vertical' &&
        !(parseInt(status, 2) & parseInt(direction, 2))
      ) {
        // 有滾動條且有滾動距離且方向?yàn)榇怪睍r(shí),阻止默認(rèn)事件,即阻止頁面滾動
        // 所以原理其實(shí)是在可能會引起背景滑動穿透時(shí)禁止掉scroll事件
        // 因?yàn)槌R姇瑒哟┩傅膱鼍岸际亲釉夭粷L動引起背景滾動,或者子元素已經(jīng)滾動到頂部或者底部,繼續(xù)滑動的話就會滑動穿透,如果發(fā)現(xiàn)已經(jīng)滾動到頂部或者底部時(shí)直接禁止掉touchmove就可以阻止滑動穿透了
        preventDefault(event, true);
      }
    },
    renderOverlay() {
      if (this.$isServer || !this.value) {
        return;
      }
      this.$nextTick(() => {
        this.updateZIndex(this.overlay ? 1 : 0);
        if (this.overlay) {
          openOverlay(this, {
            zIndex: context.zIndex++,
            duration: this.duration,
            className: this.overlayClass,
            customStyle: this.overlayStyle
          });
        } else {
          closeOverlay(this);
        }
      });
    },
    updateZIndex(value = 0) {
      this.$el.style.zIndex = ++context.zIndex + value;
    }
  }
};

來看看touch的處理,可以看到給touchstart及touchmove綁定了滑動方向及距離得計(jì)算,touchmove這個(gè)方法會在ontouchmove中被調(diào)用,注意名稱,不要混淆。

import Vue from 'vue';
const MIN_DISTANCE = 10;
function getDirection(x: number, y: number) {
  if (x > y && x > MIN_DISTANCE) {
    return 'horizontal';
  }
  if (y > x && y > MIN_DISTANCE) {
    return 'vertical';
  }
  return '';
}
type TouchMixinData = {
  startX: number;
  startY: number;
  deltaX: number;
  deltaY: number;
  offsetX: number;
  offsetY: number;
  direction: string;
};
export const TouchMixin = Vue.extend({
  data() {
    return { direction: '' } as TouchMixinData;
  },
  methods: {
    // touchstart獲取起始位置
    touchStart(event: TouchEvent) {
      this.resetTouchStatus();
      this.startX = event.touches[0].clientX;
      this.startY = event.touches[0].clientY;
    },
    // touchmove算得移動后得位移差,用來計(jì)算方向和偏移量
    touchMove(event: TouchEvent) {
      const touch = event.touches[0];
      this.deltaX = 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;
    }
  }
});

到此,關(guān)于“vue中pc移動滾動穿透問題如何解決”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI