欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue 實(shí)現(xiàn)微信浮標(biāo)效果

 更新時(shí)間:2019年09月01日 09:26:21   作者:小黎也  
微信的浮窗,大伙應(yīng)該都用過,當(dāng)我們正在閱讀一篇公眾號(hào)文章時(shí),突然需要處理微信消息,點(diǎn)擊浮窗,在微信上會(huì)有個(gè)浮標(biāo),點(diǎn)擊浮標(biāo)可以再次回到文章。今天小編抽空給大家介紹vue 實(shí)現(xiàn)微信浮標(biāo)效果,感興趣的朋友一起看看吧

微信的浮窗,大伙應(yīng)該都用過,當(dāng)我們正在閱讀一篇公眾號(hào)文章時(shí),突然需要處理微信消息,點(diǎn)擊浮窗,在微信上會(huì)有個(gè)浮標(biāo),點(diǎn)擊浮標(biāo)可以再次回到文章。

我們今天打算擼一個(gè)類似微信的浮標(biāo)組件,我們期望組件有以下功能

  • 支持拖拽
  • 支持左右吸附
  • 支持頁面上下滑動(dòng)時(shí)隱藏

效果預(yù)覽

 

拖拽事件

浮標(biāo)的核心功能的就是拖拽,對(duì)鼠標(biāo)或移動(dòng)端的觸摸的事件來說,有三個(gè)階段,鼠標(biāo)或手指接觸到元素時(shí),鼠標(biāo)或手指在移動(dòng)的過程,鼠標(biāo)或手指離開元素。這個(gè)三個(gè)階段對(duì)應(yīng)的事件名稱如下:

mouse: {
  start: 'mousedown',
  move: 'mousemove',
  stop: 'mouseup'
},
touch: {
  start: 'touchstart',
  move: 'touchmove',
  stop: 'touchend'
}

元素定位

滑動(dòng)容器我們采用絕對(duì)定位,通過設(shè)置 top 和 left 屬性來改變?cè)氐奈恢?,那我們?cè)趺传@取到新的 top 和 left 呢?

我們先看下面這張圖

 

黃色區(qū)域是拖拽的元素,藍(lán)色的點(diǎn)就是鼠標(biāo)或手指觸摸的位置,在元素移動(dòng)的過程中,這些值也會(huì)隨著發(fā)生改變,那么我們只要計(jì)算出新的觸摸位置和最初觸摸位置的橫坐標(biāo)和豎坐標(biāo)的變化,就可以算出移動(dòng)后的 top left ,因?yàn)橥献У脑夭浑S著頁面滾動(dòng)而變化,所以我們采用 pageX pageY 這兩個(gè)值。用公式簡(jiǎn)單描述就是;

newTop = initTop + (currentPageY - initPageY)
newLeft = initLeft + (currentPageX - initPageX)

拖拽區(qū)域

拖拽區(qū)域默認(rèn)是在拖拽元素的父級(jí)元素內(nèi),所以我們需要計(jì)算出父級(jí)元素的寬高。這里有一點(diǎn)需要注意,如果父級(jí)的寬高是由異步事件來改變的,那么獲取的時(shí)候就會(huì)不準(zhǔn)確,這種情況就需要改變下布局。

private getParentSize() {
  const style = window.getComputedStyle(
    this.$el.parentNode as Element,
    null
  );

  return [
    parseInt(style.getPropertyValue('width'), 10),
    parseInt(style.getPropertyValue('height'), 10)
  ];

}

拖拽的前中后

有了上面的基礎(chǔ),我們分析下拖拽的三個(gè)階段我們需要做哪些工作

  • 觸摸元素,即開始拖拽,將當(dāng)前元素的 top left 和觸摸點(diǎn)的 pageX pageY 用對(duì)象存儲(chǔ)起來,然后監(jiān)聽移動(dòng)和結(jié)束事件
  • 元素拖拽過程,計(jì)算當(dāng)前的 pageX pageY 與 初始的 pageX pageY 的差值,算出當(dāng)前的 top left ,更新元素的位置
  • 拖拽結(jié)束,重置初始值

左右吸附

在手指離開后,若元素偏向某一側(cè),便吸附在該側(cè)的邊上,那么在拖拽事件結(jié)束后,根據(jù)元素的X軸中心的與父級(jí)元素的X軸中心點(diǎn)做比較,就可知道往左還是往右移動(dòng)

頁面上下滑動(dòng)時(shí)隱藏

使用 watch 監(jiān)聽父級(jí)容器的滑動(dòng)事件,獲取 scrollTop ,當(dāng) scrollTop 的值不在發(fā)生變化的時(shí)候,就說明頁面滑動(dòng)結(jié)束了,在變化前和結(jié)束時(shí)設(shè)置 left 即可。

若無法監(jiān)聽父級(jí)容器滑動(dòng)事件,那么可以將監(jiān)聽事件放到外層組件,將 scrollTop 傳入拖拽組件也是可以的。

代碼實(shí)現(xiàn)

組件用的是 ts 寫的,代碼略長(zhǎng),大伙可以先收藏在看

// draggable.vue
<template>
  <div class="dra " :class="{'dra-tran':showtran}" :style="style" @mousedown="elementTouchDown" @touchstart="elementTouchDown">
    <slot></slot>
  </div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import dom from './dom';
const events = {
  mouse: {
    start: 'mousedown',
    move: 'mousemove',
    stop: 'mouseup'
  },
  touch: {
    start: 'touchstart',
    move: 'touchmove',
    stop: 'touchend'
  }
};
const userSelectNone = {
  userSelect: 'none',
  MozUserSelect: 'none',
  WebkitUserSelect: 'none',
  MsUserSelect: 'none'
};
const userSelectAuto = {
  userSelect: 'auto',
  MozUserSelect: 'auto',
  WebkitUserSelect: 'auto',
  MsUserSelect: 'auto'
};
@Component({
  name: 'draggable',
})
export default class Draggable extends Vue {
  @Prop(Number) private width !: number; // 寬
  @Prop(Number) private height !: number; // 高
  @Prop({ type: Number, default: 0 }) private x!: number; //初始x
  @Prop({ type: Number, default: 0 }) private y!: number; //初始y
  @Prop({ type: Number, default: 0 }) private scrollTop!: number; // 初始 scrollTop
  @Prop({ type: Boolean,default:true}) private draggable !:boolean; // 是否開啟拖拽
  @Prop({ type: Boolean,default:true}) private adsorb !:boolean; // 是否開啟吸附左右兩側(cè)
  @Prop({ type: Boolean,default:true}) private scrollHide !:boolean; // 是否開啟滑動(dòng)隱藏
  private rawWidth: number = 0; 
  private rawHeight: number = 0; 
  private rawLeft: number = 0; 
  private rawTop: number = 0;
  private top: number = 0; // 元素的 top
  private left: number = 0; // 元素的 left
  private parentWidth: number = 0; // 父級(jí)元素寬
  private parentHeight: number = 0; // 父級(jí)元素高
  private eventsFor = events.mouse; // 監(jiān)聽事件
  private mouseClickPosition = { // 鼠標(biāo)點(diǎn)擊的當(dāng)前位置
    mouseX: 0,
    mouseY: 0,
    left: 0,
    top: 0,
  };
  private bounds = {
    minLeft: 0,
    maxLeft: 0,
    minTop: 0,
    maxTop: 0,
  };
  private dragging: boolean = false;
  private showtran: boolean = false;
  private preScrollTop: number = 0;
  private parentScrollTop: number = 0;
  private mounted() {
    this.rawWidth = this.width;
    this.rawHeight = this.height;
    this.rawLeft = this.x;
    this.rawTop = this.y;
    this.left = this.x;
    this.top = this.y;
    [this.parentWidth, this.parentHeight] = this.getParentSize();
    // 對(duì)邊界計(jì)算
    this.bounds = this.calcDragLimits();
    if(this.adsorb){
      dom.addEvent(this.$el.parentNode,'scroll',this.listScorll)
    }
  }
  private listScorll(e:any){
    this.parentScrollTop = e.target.scrollTop
  }
  private beforeDestroy(){
    dom.removeEvent(document.documentElement, 'touchstart', this.elementTouchDown);
    dom.removeEvent(document.documentElement, 'mousedown', this.elementTouchDown);
    dom.removeEvent(document.documentElement, 'touchmove', this.move);
    dom.removeEvent(document.documentElement, 'mousemove', this.move);
    dom.removeEvent(document.documentElement, 'mouseup', this.handleUp);
    dom.removeEvent(document.documentElement, 'touchend', this.handleUp);
  }
  private getParentSize() {
    const style = window.getComputedStyle(
      this.$el.parentNode as Element,
      null
    );
    return [
      parseInt(style.getPropertyValue('width'), 10),
      parseInt(style.getPropertyValue('height'), 10)
    ];
  }
  /**
   * 滑動(dòng)區(qū)域計(jì)算
   */
  private calcDragLimits() {
    return {
      minLeft: 0,
      maxLeft: Math.floor(this.parentWidth - this.width),
      minTop: 0,
      maxTop: Math.floor(this.parentHeight - this.height),
    };
  }
  /**
   * 監(jiān)聽滑動(dòng)開始
   */
  private elementTouchDown(e: TouchEvent) {
    if(this.draggable){
      this.eventsFor = events.touch;
      this.elementDown(e);
    }
  }
  private elementDown(e: TouchEvent | MouseEvent) {
    const target = e.target || e.srcElement;
    this.dragging = true;
    this.mouseClickPosition.left = this.left;
    this.mouseClickPosition.top = this.top;
    this.mouseClickPosition.mouseX = (e as TouchEvent).touches
      ? (e as TouchEvent).touches[0].pageX
      : (e as MouseEvent).pageX;
    this.mouseClickPosition.mouseY = (e as TouchEvent).touches
      ? (e as TouchEvent).touches[0].pageY
      : (e as MouseEvent).pageY;
    // 監(jiān)聽移動(dòng)事件 結(jié)束事件
    dom.addEvent(document.documentElement, this.eventsFor.move, this.move);
    dom.addEvent(
      document.documentElement,
      this.eventsFor.stop,
      this.handleUp
    );
  }
  
  /**
   * 監(jiān)聽拖拽過程
   */
  private move(e: TouchEvent | MouseEvent) {
    if(this.dragging){
      this.elementMove(e);
    }
  }
  private elementMove(e: TouchEvent | MouseEvent) {
    const mouseClickPosition = this.mouseClickPosition;
    const tmpDeltaX = mouseClickPosition.mouseX - ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageX : (e as MouseEvent).pageX) || 0;
    const tmpDeltaY = mouseClickPosition.mouseY - ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageY : (e as MouseEvent).pageY) || 0;
    if (!tmpDeltaX && !tmpDeltaY) return;
    this.rawTop = mouseClickPosition.top - tmpDeltaY;
    this.rawLeft = mouseClickPosition.left - tmpDeltaX;
    this.$emit('dragging', this.left, this.top);
  }
  /**
   * 監(jiān)聽滑動(dòng)結(jié)束
   */
  private handleUp(e: TouchEvent | MouseEvent) {
    this.rawTop = this.top;
    this.rawLeft = this.left;
    if (this.dragging) {
      this.dragging = false;
      this.$emit('dragstop', this.left, this.top);
    }
    // 左右吸附
    if(this.adsorb){
      this.showtran = true
      const middleWidth = this.parentWidth / 2;
      if((this.left + this.width/2) < middleWidth){
        this.left = 0
      }else{
        this.left = this.bounds.maxLeft - 10
      }
      setTimeout(() => {
        this.showtran = false
      }, 400);
    }
    this.resetBoundsAndMouseState();
  }
  /**
   * 重置初始數(shù)據(jù)
   */
  private resetBoundsAndMouseState() {
    this.mouseClickPosition = {
      mouseX: 0,
      mouseY: 0,
      left: 0,
      top: 0,
    };
  }
  /**
   * 元素位置
   */
  private get style() {
    return {
      position: 'absolute',
      top: this.top + 'px',
      left: this.left + 'px',
      width: this.width + 'px',
      height: this.height + 'px',
      ...(this.dragging ? userSelectNone : userSelectAuto)
    };
  }
  @Watch('rawTop')
  private rawTopChange(newTop: number) {
    const bounds = this.bounds;
    if (bounds.maxTop === 0) {
      this.top = newTop;
      return;
    }
    const left = this.left;
    const top = this.top;
    if (bounds.minTop !== null && newTop < bounds.minTop) {
      newTop = bounds.minTop;
    } else if (bounds.maxTop !== null && bounds.maxTop < newTop) {
      newTop = bounds.maxTop;
    }
    this.top = newTop;
  }
  @Watch('rawLeft')
  private rawLeftChange(newLeft: number) {
    const bounds = this.bounds;
    if (bounds.maxTop === 0) {
      this.left = newLeft;
      return;
    }
    const left = this.left;
    const top = this.top;
    if (bounds.minLeft !== null && newLeft < bounds.minLeft) {
      newLeft = bounds.minLeft;
    } else if (bounds.maxLeft !== null && bounds.maxLeft < newLeft) {
      newLeft = bounds.maxLeft;
    }
    this.left = newLeft;
  }
  @Watch('scrollTop') // 監(jiān)聽 props.scrollTop 
  @Watch('parentScrollTop') // 監(jiān)聽父級(jí)組件
  private scorllTopChange(newTop:number){
    let timer = undefined;
    if(this.scrollHide){
      clearTimeout(timer);
      this.showtran = true;
      this.preScrollTop = newTop;
      this.left = this.bounds.maxLeft + this.width - 10
      timer = setTimeout(()=>{
        if(this.preScrollTop === newTop ){
          this.left = this.bounds.maxLeft - 10;
          setTimeout(()=>{
            this.showtran = false;
          },300)
        }
      },200)
    }
  }
} 
</script>
<style lang="css" scoped>
.dra {
  touch-action: none;
}
.dra-tran {
  transition: top .2s ease-out , left .2s ease-out;
}
</style>
// dom.ts
export default {
  addEvent(el: any, event: string, handler: any) {
    if (!el) {
      return;
    }
    if (el.attachEvent) {
      el.attachEvent('on' + event, handler);
    } else if (el.addEventListener) {
      el.addEventListener(event, handler, true);
    } else {
      el['on' + event] = handler;
    }
  },
  removeEvent(el: any, event: string, handler: any) {
    if (!el) {
      return;
    }
    if (el.detachEvent) {
      el.detachEvent('on' + event, handler);
    } else if (el.removeEventListener) {
      el.removeEventListener(event, handler, true);
    } else {
      el['on' + event] = null;
    }
  }

};

總結(jié)

以上所述是小編給大家介紹的vue 實(shí)現(xiàn)微信浮標(biāo)效果,希望對(duì)大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會(huì)及時(shí)回復(fù)大家的!

相關(guān)文章

  • Vue router重定向redirect如何傳值問題

    Vue router重定向redirect如何傳值問題

    這篇文章主要介紹了Vue router重定向redirect如何傳值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue中的傳值及賦值問題

    vue中的傳值及賦值問題

    這篇文章主要介紹了vue中的傳值及賦值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue實(shí)現(xiàn)websocket客服聊天功能

    vue實(shí)現(xiàn)websocket客服聊天功能

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)websocket客服聊天功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • vue3如何使用element-plus的dialog

    vue3如何使用element-plus的dialog

    這篇文章主要介紹了vue3優(yōu)雅的使用element-plus的dialog,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • 詳解vue中this.$emit()的返回值是什么

    詳解vue中this.$emit()的返回值是什么

    這篇文章主要介紹了詳解vue中this.$emit()的返回值是什么,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Vue中的components組件與props的使用解讀

    Vue中的components組件與props的使用解讀

    這篇文章主要介紹了Vue中的components組件與props的使用解讀,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • vue3+ElementPlus使用lang="ts"報(bào)Unexpected?token錯(cuò)誤的解決辦法

    vue3+ElementPlus使用lang="ts"報(bào)Unexpected?token錯(cuò)誤的解決

    最近開發(fā)中遇到了些問題,跟大家分享下,這篇文章主要給大家介紹了關(guān)于vue3+ElementPlus使用lang="ts"報(bào)Unexpected?token錯(cuò)誤的解決辦法,需要的朋友可以參考下
    2023-01-01
  • vue前端重構(gòu)computed及watch組件通信等實(shí)用技巧整理

    vue前端重構(gòu)computed及watch組件通信等實(shí)用技巧整理

    這篇文章主要為大家介紹了vue前端重構(gòu)computed及watch組件通信等實(shí)用技巧整理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • vue實(shí)現(xiàn)純前端表格滾動(dòng)分頁加載

    vue實(shí)現(xiàn)純前端表格滾動(dòng)分頁加載

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)純前端表格滾動(dòng)分頁加載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue將后臺(tái)數(shù)據(jù)時(shí)間戳轉(zhuǎn)換成日期格式

    vue將后臺(tái)數(shù)據(jù)時(shí)間戳轉(zhuǎn)換成日期格式

    這篇文章主要為大家詳細(xì)介紹了vue將后臺(tái)數(shù)據(jù)時(shí)間戳轉(zhuǎn)換成日期格式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07

最新評(píng)論