滚动穿透与滚动溢出

滚动穿透

问题描述:

在移动端开发弹框时(小程序也会出现),当弹框下的页面超过一屏时它下面的内容也会跟着一起滑动,看起来好像事件穿透到了下面的DOM元素一样

问题原因:

通过事件的表象,可以推测是文档的滚动事件被触发了,那我们就禁用滚动事件就好办了。

案例伪代码:

解决方案A✅ e.preventDefault()

小程序——脚本语法,通过e.preventDefault()阻止元素的touch-move事件。(H5同理,监听touch-move事件,阻止其touch-move事件)

<view
      :class="['asian-popup', classNames]"
      :style="{ 'z-index': zIndex }"
      @touchmove="utils.disableScrollEvent"
      v-if="visible"
      >
  <view class="asian-popup-mask" />
  <view :class="['asian-popup-content', 'asian-popup-' + position]">
      <slot />
  </view>
  </view>
function disableScrollEvent(event) {
  event.preventDefault();
}

function enableScrollEvent(event) {
  event.stopPropagation();
}

export default {
  disableScrollEvent,
  enableScrollEvent,
};

 

解决方案B✅ (touch-action)

默认情况下,平移(滚动)和缩放手势由浏览器专门处理,但是可以通过 CSS 特性 touch-action 来改变触摸手势的行为。摘取几个 touch-action 的值如下。

描述

auto

启用浏览器处理所有平移和缩放手势。

none

禁用浏览器处理所有平移和缩放手势。

manipulation

启用平移和缩放手势,但禁用其他非标准手势,例如双击缩放。

pinch-zoom

启用页面的多指平移和缩放。

于是在 popup 元素上设置该属性,禁用元素(及其不可滚动的后代)上的所有手势就可以解决该问题了。

 <view
    :class="['asian-popup', classNames]"
    :style="{ 'z-index': zIndex,touchAction:'none' }"
    v-if="visible"
  >
    <view class="asian-popup-mask" />
    <view :class="['asian-popup-content', 'asian-popup-' + position]">

        <slot />
    </view>
  </view>

 

滚动溢出

问题描述:

弹窗内也含有滚动元素,在滚动元素滚到底部或顶部时,再往下或往上滚动,也会触发页面的滚动,这种现象称之为滚动链

解决方案-A✅ (disable-lower/upper-scroll)

支付宝小程序官方提供的 scroll-view 组件,使用 disable-lower-scroll 与 disable-upper-scroll 属性可以解决问题.

背后的原理是,当组件滚动到底部或顶部时,通过调用 event.preventDefault 阻止了所有滚动,从而页面滚动也不会触发了,而在滚动之间则不做处理。

<view
      :class="['asian-popup', classNames]"
      :style="{ 'z-index': zIndex }"
      @touchmove="utils.disableScrollEvent"
      v-if="visible"
      >
  <view class="asian-popup-mask" />
  <view :class="['asian-popup-content', 'asian-popup-' + position]">
    <scroll-view
                 @touchmove="utils.enableScrollEvent"
                 :style="{maxHeight:contentMaxHeight}"
                 :scroll-y="true"
                 disable-lower-scroll="out-of-bounds"
                 disable-upper-scroll="out-of-bounds"
                 >
      <slot />
  </scroll-view>
  </view>
  </view>

 

解决方案完整 Demo

小程序使用sjs

<template>
<view
      :class="['asian-popup', classNames]"
      :style="{ 'z-index': zIndex }"
      @touchmove="utils.disableScrollEvent"
      v-if="visible"
      >
  <view class="asian-popup-mask" />
  <view :class="['asian-popup-content', 'asian-popup-' + position]">
    <scroll-view
                 @touchmove="utils.enableScrollEvent"
                 :style="{maxHeight:contentMaxHeight}"
                 :scroll-y="true"
                 disable-lower-scroll="out-of-bounds"
                 disable-upper-scroll="out-of-bounds"
                 >
      <slot />
  </scroll-view>
  </view>
  </view>
</template>
<script module="utils" lang="sjs" src="./index.sjs"></script>
<script lang="ts">
  import { Component, Vue, Prop } from 'vue-property-decorator';
  @Component({
    components: {},
  })
  export default class Popup extends Vue {
    @Prop({ type: String, default: '' }) private classNames;
    @Prop({ type: String, default: 'center' }) private position;
    @Prop({ type: Number, default: 998 }) private zIndex;
    @Prop({ type: Boolean, default: true }) private animations;
    @Prop({ type: Number, default: 300 }) private duration;
    @Prop({ type: Boolean, default: true }) private visible;
    @Prop({ type: String, default: '500rpx' }) private contentMaxHeight
  }
</script>

<style lang="less">
  @popupPrefix: asian-popup;
  .@{popupPrefix} {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 998;
    &-mask {
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.55);
    }
    &-content {
      position: fixed;
      background-color: #fff;
      z-index: 2;
      padding: 24rpx;
      display: flex;
      flex-direction: column;
      height: auto;
    }
    &-top {
      top: 0;
      left: 0;
      right: 0;
      animation-name: amd-popup-top;
      border-radius: 0 0 8rpx 8rpx;
    }
    &-right {
      top: 0;
      right: 0;
      bottom: 0;
      animation-name: amd-popup-right;
      width: 500rpx;
    }
    &-bottom {
      left: 0;
      right: 0;
      bottom: 0;
      animation-name: amd-popup-bottom;
      border-radius: 8rpx 8rpx 0 0;
    }
    &-left {
      top: 0;
      left: 0;
      bottom: 0;
      animation-name: amd-popup-left;
      width: 500rpx;
    }
    &-center {
      min-width: 500rpx;
      top: 50%;
      left: 50%;
      transform: translate3d(-50%, -50%, 0);
      animation-name: amd-popup-center;
      border-radius: 8rpx;
    }
  }
  @keyframes amd-popup-top {
    0% {
      top: -100%;
    }
    100% {
      top: 0;
    }
  }
  
  @keyframes amd-popup-bottom {
    0% {
      bottom: -100%;
    }
    100% {
      bottom: 0;
    }
  }
  
  @keyframes amd-popup-left {
    0% {
      left: -100%;
    }
    100% {
      left: 0;
    }
  }
  
  @keyframes amd-popup-right {
    0% {
      right: -100%;
    }
    100% {
      right: 0;
    }
  }
  
  @keyframes amd-popup-center {
    0% {
      transform: translate3d(-50%, -50%, 0) scale(0.1);
      opacity: 0;
    }
    70% {
      transform: translate3d(-50%, -50%, 0) scale(1.2);
      opacity: 1;
    }
    80% {
      transform: translate3d(-50%, -50%, 0) scale(0.95);
    }
    85% {
      transform: translate3d(-50%, -50%, 0) scale(1.1);
      opacity: 0.9;
    }
    95% {
      transform: translate3d(-50%, -50%, 0) scale(0.97);
      opacity: 1;
    }
    100% {
      transform: translate3d(-50%, -50%, 0) scale(1);
    }
  }
</style>
function disableScrollEvent(event) {
  event.preventDefault();
}

function enableScrollEvent(event) {
  event.stopPropagation();
}

export default {
  disableScrollEvent,
  enableScrollEvent,
};

 

使用touch-action: none

<template>
  <view
    :class="['asian-popup', classNames]"
    :style="{ 'z-index': zIndex }"
    v-if="visible"
  >
    <view class="asian-popup-mask" />
    <view :class="['asian-popup-content', 'asian-popup-' + position]">
      <scroll-view
        :style="{maxHeight:contentMaxHeight}"
        :scroll-y="true"
        disable-lower-scroll="out-of-bounds"
        disable-upper-scroll="out-of-bounds"
      >
        <slot />
      </scroll-view>
    </view>
  </view>
</template>

<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
@Component({
  components: {},
})
export default class Popup extends Vue {
  @Prop({ type: String, default: '' }) private classNames;
  @Prop({ type: String, default: 'center' }) private position;
  @Prop({ type: Number, default: 998 }) private zIndex;
  @Prop({ type: Boolean, default: true }) private animations;
  @Prop({ type: Number, default: 300 }) private duration;
  @Prop({ type: Boolean, default: true }) private visible;
  @Prop({ type: String, default: '500rpx' }) private contentMaxHeight
}
</script>

<style lang="less">
@popupPrefix: asian-popup;
.@{popupPrefix} {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 998;
  touch-action: none;
  &-mask {
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.55);
  }
  &-content {
    position: fixed;
    background-color: #fff;
    z-index: 2;
    padding: 24rpx;
    display: flex;
    flex-direction: column;
    height: auto;
  }
  &-top {
    top: 0;
    left: 0;
    right: 0;
    animation-name: amd-popup-top;
    border-radius: 0 0 8rpx 8rpx;
  }
  &-right {
    top: 0;
    right: 0;
    bottom: 0;
    animation-name: amd-popup-right;
    width: 500rpx;
  }
  &-bottom {
    left: 0;
    right: 0;
    bottom: 0;
    animation-name: amd-popup-bottom;
    border-radius: 8rpx 8rpx 0 0;
  }
  &-left {
    top: 0;
    left: 0;
    bottom: 0;
    animation-name: amd-popup-left;
    width: 500rpx;
  }
  &-center {
    min-width: 500rpx;
    top: 50%;
    left: 50%;
    transform: translate3d(-50%, -50%, 0);
    animation-name: amd-popup-center;
    border-radius: 8rpx;
  }
}
@keyframes amd-popup-top {
  0% {
    top: -100%;
  }
  100% {
    top: 0;
  }
}

@keyframes amd-popup-bottom {
  0% {
    bottom: -100%;
  }
  100% {
    bottom: 0;
  }
}

@keyframes amd-popup-left {
  0% {
    left: -100%;
  }
  100% {
    left: 0;
  }
}

@keyframes amd-popup-right {
  0% {
    right: -100%;
  }
  100% {
    right: 0;
  }
}

@keyframes amd-popup-center {
  0% {
    transform: translate3d(-50%, -50%, 0) scale(0.1);
    opacity: 0;
  }
  70% {
    transform: translate3d(-50%, -50%, 0) scale(1.2);
    opacity: 1;
  }
  80% {
    transform: translate3d(-50%, -50%, 0) scale(0.95);
  }
  85% {
    transform: translate3d(-50%, -50%, 0) scale(1.1);
    opacity: 0.9;
  }
  95% {
    transform: translate3d(-50%, -50%, 0) scale(0.97);
    opacity: 1;
  }
  100% {
    transform: translate3d(-50%, -50%, 0) scale(1);
  }
}
</style>

 

posted @ 2022-06-08 11:51  无忧lv  阅读(196)  评论(0编辑  收藏  举报