微信的浮窗,大伙应该都用过,当我们正在阅读一篇公众号文章时,突然需要处理微信消息,点击浮窗,在微信上会有个浮标,点击浮标可以再次回到文章。

我们今天打算撸一个类似微信的浮标组件,我们期望组件有以下功能

支持拖拽

支持左右吸附

支持页面上下滑动时隐藏

效果预览

拖拽事件

浮标的核心功能的就是拖拽,对鼠标或移动端的触摸的事件来说,有三个阶段,鼠标或手指接触到元素时,鼠标或手指在移动的过程,鼠标或手指离开元素。这个三个阶段对应的事件名称如下:

mouse: {

start: 'mousedown',

move: 'mousemove',

stop: 'mouseup'

},

touch: {

start: 'touchstart',

move: 'touchmove',

stop: 'touchend'

}

元素定位

滑动容器我们采用绝对定位,通过设置 top 和 left 属性来改变元素的位置,那我们怎么获取到新的 top 和 left 呢?

我们先看下面这张图

黄色区域是拖拽的元素,蓝色的点就是鼠标或手指触摸的位置,在元素移动的过程中,这些值也会随着发生改变,那么我们只要计算出新的触摸位置和最初触摸位置的横坐标和竖坐标的变化,就可以算出移动后的 top left ,因为拖拽的元素不随着页面滚动而变化,所以我们采用 pageX pageY 这两个值。用公式简单描述就是;

newTop = initTop + (currentPageY - initPageY)

newLeft = initLeft + (currentPageX - initPageX)

拖拽区域

拖拽区域默认是在拖拽元素的父级元素内,所以我们需要计算出父级元素的宽高。这里有一点需要注意,如果父级的宽高是由异步事件来改变的,那么获取的时候就会不准确,这种情况就需要改变下布局。

private getParentSize() {

const style = window.getComputedStyle(

this.$el.parentNode as Element,

null

);

return [

parseInt(style.getPropertyValue('width'), 10),

parseInt(style.getPropertyValue('height'), 10)

];

}

拖拽的前中后

有了上面的基础,我们分析下拖拽的三个阶段我们需要做哪些工作

触摸元素,即开始拖拽,将当前元素的 top left 和触摸点的 pageX pageY 用对象存储起来,然后监听移动和结束事件

元素拖拽过程,计算当前的 pageX pageY 与 初始的 pageX pageY 的差值,算出当前的 top left ,更新元素的位置

拖拽结束,重置初始值

左右吸附

在手指离开后,若元素偏向某一侧,便吸附在该侧的边上,那么在拖拽事件结束后,根据元素的X轴中心的与父级元素的X轴中心点做比较,就可知道往左还是往右移动

页面上下滑动时隐藏

使用 watch 监听父级容器的滑动事件,获取 scrollTop ,当 scrollTop 的值不在发生变化的时候,就说明页面滑动结束了,在变化前和结束时设置 left 即可。

若无法监听父级容器滑动事件,那么可以将监听事件放到外层组件,将 scrollTop 传入拖拽组件也是可以的。

代码实现

组件用的是 ts 写的,代码略长,大伙可以先收藏在看

// draggable.vue

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; // 是否开启吸附左右两侧

@Prop({ type: Boolean,default:true}) private scrollHide !:boolean; // 是否开启滑动隐藏

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; // 父级元素宽

private parentHeight: number = 0; // 父级元素高

private eventsFor = events.mouse; // 监听事件

private mouseClickPosition = { // 鼠标点击的当前位置

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();

// 对边界计算

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)

];

}

/**

* 滑动区域计算

*/

private calcDragLimits() {

return {

minLeft: 0,

maxLeft: Math.floor(this.parentWidth - this.width),

minTop: 0,

maxTop: Math.floor(this.parentHeight - this.height),

};

}

/**

* 监听滑动开始

*/

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;

// 监听移动事件 结束事件

dom.addEvent(document.documentElement, this.eventsFor.move, this.move);

dom.addEvent(

document.documentElement,

this.eventsFor.stop,

this.handleUp

);

}

/**

* 监听拖拽过程

*/

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);

}

/**

* 监听滑动结束

*/

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();

}

/**

* 重置初始数据

*/

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') // 监听 props.scrollTop

@Watch('parentScrollTop') // 监听父级组件

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)

}

}

}

.dra {

touch-action: none;

}

.dra-tran {

transition: top .2s ease-out , left .2s ease-out;

}

// 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;

}

}

};

总结

以上所述是小编给大家介绍的vue 实现微信浮标效果,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

mysql浮标_vue 实现微信浮标效果相关推荐

  1. mysql编写倒计时_微信公众号开发,实现倒计时的一个功能(纯代码)

    微信公众号开发,实现倒计时的一个功能(纯代码),请在,公众,订单,代码,时间 微信公众号开发,实现倒计时的一个功能(纯代码) 易采站长站,站长之家为您整理了微信公众号开发,实现倒计时的一个功能(纯代码 ...

  2. 仿照微信的效果,实现了一个支持多选、选原图和视频的图片选择器

    代码地址如下: http://www.demodashi.com/demo/11689.html 重要提示: 1. 1.9.0版本已发布,移除了"prefs:root="的调用,这 ...

  3. 自定义实现微信通讯录效果View

    欢迎访问我的个人独立博客 ittiger.cn,原创文章,未经允许不得随意转载. 前言 在使用App过程中,经常会有使用到联系人或城市列表的场景,其实这两种效果是一样的,都是右边有个索引列表,点击索引 ...

  4. flutter 微信语言选择_Flutter 模仿微信读书效果!

    原标题:Flutter 模仿微信读书效果! 作者:xq9527链接:https://www.jianshu.com/p/ee9d30ba2c42 前言 各位同学大家好,有一段时间没有给大家更新博客了, ...

  5. 微信小程序+jsp+Mysql 电影详情的微信小程序 源代码+设计文档+说明文档

    绪论 微信小程序一经上线,其根植于场景的特性使得到了大量用户的青睐,服务项目涵盖了生活的各个方面,在手机上查找电影,早已是人们生活普遍的生活习惯.阐述了微信小程序开发的电影详情的过程,基于以上微信的发 ...

  6. 安卓作业----慕课移动应用开发作业15之模仿实现微信界面效果(二)

    此篇综合运用自定义ActionBar.ContextMenu.PopupWindow.Fragment.ViewPager 以及RecyclerView等实现微信页面效果. 同时这也是中国大学慕课移动 ...

  7. h5语音聊天audio实战|仿微信语音效果|h5即时聊天系统

    最近一段时间不是那么忙,就抽空整理了下之前的项目,因为之前有开发过H5聊天项目,只是觉得好些功能都没有特别的完善,所以就把之前项目重新开发了下,如是就有了这个html5版实时聊天语音项目weChatI ...

  8. 安卓:ListView组件实现微信通讯录效果(我的王者队友们)

    ListView控件是列表视图展示,排列方式是纵向. ListView组件实现微信通讯录效果,包含头像和文字,不能用entries这个属性来添加,要用SimpleAdapter适配器来添加数据 布局: ...

  9. mysql保存和读取微信头像

    mysql 保存和读取微信头像 时间:2019年5月5日11:48:44 Util代码: import lombok.extern.slf4j.Slf4j; import javax.sql.rows ...

最新文章

  1. Linux中的简单文本处理
  2. 人人直播带货的时代,推荐模型为什么比不过李佳琦们?
  3. java解析带斜杠的参数_Java Spring MVC应用程序仅接受带有斜杠的POST请求
  4. mysql生成随机时间
  5. 计算机科学NIP,焦点:网络入侵防护(NIP)技术真的成了鸡肋吗? -电脑资料
  6. 在unity向量空间内绘制几何(2):计算球体的表面坐标
  7. domain name
  8. Dev-C++下载与安装教程
  9. dubbo 视频教程
  10. 文件搜索工具终极大PK挑战赛
  11. [病毒木马] LSP劫持
  12. POJ3295 Tautology(栈+思路详解)
  13. 物联网开发板设计笔记 (1/7)__ 设计思路
  14. 自学 1 年进大厂,这位硬核 Linux 大佬你还不知道?
  15. 机器人的弊议论文_机器人的利与弊议论文
  16. Linux运维-day44-综合架构-playbook剧本的变量、条件语句及循环语句
  17. rancid+CVS+cvsweb部署
  18. reduce()方法详解
  19. matlab7安装问题解决办法
  20. Kubernetes:Pod

热门文章

  1. 金蝶K3 WISE,销售订单即时库存显示不正确,和实际库存不付
  2. 容易调剂?盘点计算机考研交叉学科!
  3. 模式识别边肇祺第二章(一)
  4. DRF框架之十大接口、视图家族
  5. 只要我能跑,没什么不能解决
  6. 字符串转int数据类型的三种方式
  7. C#基础系列08 - NTLM 基础学习
  8. 工具-自动获取/校对xpath helper
  9. 如何用AR引擎技术, 5步优雅实现物体识别和跟踪
  10. 使用CG MAGIC这款插件都遇到了哪些常见问题?