/**
-
初始化阶段
-
@param {HTMLElement} el 待待绑定的元素
-
@param {Object} binding Vue binding集合
*/
inserted(el, binding) {
// 容错判断,排除非DOM
if (!(el instanceof HTMLElement)) {
console.error(“类型错误,绑定对象必须是HTMLElement”);
return false;
}
},
我们知道,自定义指令也是有钩子函数的,示例中使用的是**inserted()**这个钩子函数,这个函数表示:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中),并且这个函数存在两个参数,这里我们用到了第一个:
- el:代表当前被绑定的DOM元素;
既然是验证,那么我们肯定要验证一下这个el是不是HTMLElement类型,只要是这个类型,那么就代表这个自定义指令绑定的对象是DOM元素;
合并动态参数
在这一步,我们需要将用户自定义的动态参数和v-move中预设好的默认参数进行合并,合并的规则是:如果用户设定了参数那么用户设定的优先,如果没有设定那么就启用默认参数,比如,我们默认拖动的DOM和实际移动的DOM是同一个,但弹窗就相对比较特殊,弹窗是拖动的是title部分,移动的确实整个弹窗整体;
/**
-
处理动态参数
-
@param {String | Obejct} params 动态参数原始数据
-
@returns {Obejct} 期望的动态参数对象
*/
export function handleParams(params) {
const data = {};
Object.assign(data, DEFAULT_CONFIG);
// 动态参数类型
switch (commom.getType(params)) {
// 字符串
case “[object String]”:
data.move = params;
data.click = params;
break;
// 对象
case “[object Object]”:
for (let item in data) {
if (!Object.prototype.hasOwnProperty.call(data, item)) continue;
// 容错,判断是否为undefined
data[item] = commom.isUndefined(params[item])
-
? data[item]
- params[item];
}
break;
default:
// 启用默认参数
break;
}
return data;
}
在这里,我们允许用户输入的动态参数类型有两种,一种是字符串类型,一种是对象类型,除此之外的所有类型都会被认为是非法类型:
-
字符串:那么我们就认为拖动和实际移动的DOM是同一种;
-
对象:根据key进行一次遍历,并逐一对key对应的值进行覆盖;
最后会得到一个全新的合法的动态参数,并返回;
合并修饰符
动态参数合并完了,那么紧接着就是合并修饰符,和动态参数一样的规则:用户设定优先,如果没有设定,启动默认修饰符,这一块主要合并的是一些比如是否可以超屏拖动的参数,比如设定:
// 允许超屏拖动
开启超屏拖动后,那么被拖动的DOM边界可以超出屏幕之外,这个功能是什么用处呢?具体举例一下,比如:实际项目中图片预览功能,某张图片被放大后,因为视角不方便,需要将图片中待阅读的部分拖动到屏幕中央,因为图片放大后肯定有部分会被移出屏幕,因此就需要超屏拖动了,如果不开启,那么被拖动的DOM永远只能在屏幕内部拖动;
具体参数合并代码如下:
/**
-
处理修饰符
-
@param {Object} modifiers 修饰符原始数据
-
@returns {Object} 期望的修饰符对象
*/
export function handleModifiers(modifiers) {
// 容错判断,排除非对象
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
return {
dirPosition: ABSOLUTE,
dirAll: modifiers.all ? ALL : null,
};
}
挺简单的代码,首先是判断了修饰符参数是否是一个对象,做了一个简单的容错,其实这里不对,如果修饰符如果设定异常,那么返回的应该是默认参数,而不是false,这里有问题;
拖动验证
肯定有小伙伴觉得奇怪,为什么会有一个拖动验证,很简单,有时候这个拖动功能也是需要开启的,比如,有时候我们希望这个DOM默认是不可以拖动的,只有当开启了某个开关后才能拖动,用法示例:
// isMove判断当前拖动是否生效,true-可拖动,false-不可拖动
代码上的话,我们只需要对这个value值做一个判断即可,如果vlaue值是false,直接返回,不给走代码就行了
// false-不可拖动
if (commom.isBol(binding.value) && !binding.value) {
return false;
}
初始化坐标
这一步中,主要的作用就是初始化DOM的坐标,一是我们得获取到当前DOM现有的坐标,二是初始化一些下面步骤中需要使用到的变量
// 坐标,这个是初始化下面步骤需要的变量
let x = 0,
y = 0;
let offsetLeft = 0,
offsetRight = 0;
具体初始化的时机就在于我们鼠标按下的那一刻,在那一刻,我们可以获取到当前被拖动DOM的初始偏移量,我们需要将这些偏移量保存下来做计算:
// 触发鼠标左击
dom.click.onmousedown = function(event) {
// 阻止默认事件和冒泡
event.preventDefault();
event.stopPropagation();
// 设置初始坐标
x = event.pageX;
y = event.pageY;
// 设置初始偏移位置
offsetLeft = dom.move.offsetLeft;
offsetRight = dom.move.offsetTop;
onMove = true;
};
计算坐标
在计算坐标这一部分,一共有四个坐标需要计算:
-
x轴上的最小坐标,最大坐标
-
y轴上的最小坐标,最大坐标
当我们拖动DOM的时候,需要实时计算出对应的坐标
最小值计算
/**
-
计算在X轴/Y轴可拖动的最小值
-
@param {Obejct} modifiers 修饰符
-
@param {String} position X轴/Y轴
-
@param {HTMLElement} el
-
@returns {Number} 可拖动的最小距离值
*/
export function computedMin(modifiers, position, el) {
// 容错判断,排除非对象的修饰符
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
// 容错判断,排除非DOM
if (!(el instanceof HTMLElement)) {
console.error(“类型错误,绑定对象必须是HTMLElement”);
return false;
}
// 获得x轴最小值
const minXDistance = parseInt(window.getComputedStyle(el).width);
// 获得Y轴最小值
const minYDistance = parseInt(window.getComputedStyle(el).height);
// 超屏拖动
if (modifiers.dirAll === ALL) {
return position === “width” ? -minXDistance : -minYDistance;
}
// 非超屏拖动
else {
return position === “width” ? 0 : 0;
}
}
因为x轴和y轴上都存在最小值的计算,因此就通用了一下
最大值计算
/**
-
计算在X轴/Y轴可拖动的最大值
-
@param {Object} modifiers 修饰符
-
@param {String} position X轴-width,Y轴-height
-
@param {HTMLElement} el 绑定的元素
-
@returns {Number} 可拖动的最大距离值
*/
export function computedMax(modifiers, position, el) {
// 容错判断,排除非对象
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
// 容错判断,排除非DOM
if (!(el instanceof HTMLElement)) {
console.error(“类型错误,绑定对象必须是HTMLElement”);
return false;
}
// 无上级元素
if (!el.parentNode) {
return 0;
}
// 获得X轴最大值
const maxXDistance = parseInt(window.getComputedStyle(el).width);
// 获得Y轴最大值
const maxYDistance = parseInt(window.getComputedStyle(el).height);
// 超屏拖动
if (modifiers.dirAll === ALL) {
return position === “width”
-
? el.parentNode.clientWidth + maxXDistance
- el.parentNode.clientHeight + maxYDistance;
}
// 非超屏拖动
else {
return position === “width”
-
? el.parentNode.clientWidth - maxXDistance
- el.parentNode.clientHeight - maxYDistance;
}
}
同理,最大值的计算也存在x轴和y轴上的,所以也通用了一下
这样在这一步中,我们就得到了四个坐标,分别是:x轴的最小值和最大值,y轴上的最小值和最大值;
设置坐标
在这一步就是对DOM进行坐标设置了,我们需要实时修改DOM的left和top的值,达到DOM的位置的变化,当然,这部分代码需要写在mousemove中,以及鼠标松开时,移除拖动事件
// 触发鼠标拖动
document.onmousemove = function(e) {
// 判断控制阀确认是否可拖动
if (!onMove) return false;
// 初始化位置坐标
let initMouseX = e.pageX;
let initMouseY = e.pageY;
// 初始化偏移坐标
let offsetX = initMouseX - (x - offsetLeft);
let offsetY = initMouseY - (y - offsetRight);
// 获得X轴最大值
let maxX = computedMax(modifiers, “width”, dom.move);
// 获得X轴最小值
let minX = computedMin(modifiers, “width”, dom.move);
// 获得Y轴最大值
let maxY = computedMax(modifiers, “height”, dom.move);
// 获得Y轴最小值
let minY = computedMin(modifiers, “height”, dom.move);
// 拖动后坐标
offsetX = offsetX > maxX ? maxX : offsetX < minX ? minX : offsetX;
offsetY = offsetY > maxY ? maxY : offsetY < minY ? minY : offsetY;
// 设置坐标
dom.move.style.left = offsetX + “px”;
dom.move.style.top = offsetY + “px”;
};
// 触发鼠标左击释放
document.onmouseup = function() {
// 关闭控制阀
onMove = false;
// 清空状态
document.onmousemove = document.onmouseup = null;
// 非函数时返回,函数时触发函数
if (!commom.isFunction(binding.value)) {
return false;
}
binding.value.call(this);
};
当然,这里还有一个小细节,就是如果我们的value值是一个函数的时候,那么松开鼠标的时候需要触发函数
代码
以下为v-move的主体代码,有兴趣的小伙伴可以试下
/**
- @Description 本指令应用于设置DOM元素为可拖动状态,具体用法见文档
*/
// 引入工具函数
import commom from “…/…/Utils/common”;
// dom的position值
const ABSOLUTE = “absolute”;
// 是否可以超屏拖动
const ALL = “all”;
/**
-
处理修饰符
-
@param {Object} modifiers 修饰符原始数据
-
@returns {Object} 期望的修饰符对象
*/
export function handleModifiers(modifiers) {
// 容错判断,排除非对象
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
return {
dirPosition: ABSOLUTE,
dirAll: modifiers.all ? ALL : null,
};
}
// 默认参数
const DEFAULT_CONFIG = {
move: “”,
click: “”,
};
/**
-
处理动态参数
-
@param {String | Obejct} params 动态参数原始数据
-
@returns {Obejct} 期望的动态参数对象
*/
export function handleParams(params) {
const data = {};
Object.assign(data, DEFAULT_CONFIG);
// 动态参数类型
switch (commom.getType(params)) {
// 字符串
case “[object String]”:
data.move = params;
data.click = params;
break;
// 对象
case “[object Object]”:
for (let item in data) {
if (!Object.prototype.hasOwnProperty.call(data, item)) continue;
// 容错,判断是否为undefined
data[item] = commom.isUndefined(params[item])
-
? data[item]
- params[item];
}
break;
default:
// 启用默认参数
break;
}
return data;
}
/**
-
计算在X轴/Y轴可拖动的最大值
-
@param {Object} modifiers 修饰符
-
@param {String} position X轴-width,Y轴-height
-
@param {HTMLElement} el 绑定的元素
-
@returns {Number} 可拖动的最大距离值
*/
export function computedMax(modifiers, position, el) {
// 容错判断,排除非对象
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
// 容错判断,排除非DOM
if (!(el instanceof HTMLElement)) {
console.error(“类型错误,绑定对象必须是HTMLElement”);
return false;
}
// 无上级元素
if (!el.parentNode) {
return 0;
}
// 获得X轴最大值
const maxXDistance = parseInt(window.getComputedStyle(el).width);
// 获得Y轴最大值
const maxYDistance = parseInt(window.getComputedStyle(el).height);
// 超屏拖动
if (modifiers.dirAll === ALL) {
return position === “width”
-
? el.parentNode.clientWidth + maxXDistance
- el.parentNode.clientHeight + maxYDistance;
}
// 非超屏拖动
else {
return position === “width”
-
? el.parentNode.clientWidth - maxXDistance
- el.parentNode.clientHeight - maxYDistance;
}
}
/**
-
计算在X轴/Y轴可拖动的最小值
-
@param {Obejct} modifiers 修饰符
-
@param {String} position X轴/Y轴
-
@param {HTMLElement} el
-
@returns {Number} 可拖动的最小距离值
*/
export function computedMin(modifiers, position, el) {
// 容错判断,排除非对象的修饰符
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
// 容错判断,排除非DOM
if (!(el instanceof HTMLElement)) {
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
@param {String} position X轴/Y轴
-
@param {HTMLElement} el
-
@returns {Number} 可拖动的最小距离值
*/
export function computedMin(modifiers, position, el) {
// 容错判断,排除非对象的修饰符
if (!commom.isObj(modifiers)) {
console.error(“类型错误,修饰符必须是对象类型”);
return false;
}
// 容错判断,排除非DOM
if (!(el instanceof HTMLElement)) {
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-eZdUsdg4-1715721496091)]
[外链图片转存中…(img-yVwVOY4k-1715721496092)]
[外链图片转存中…(img-kJdANWQy-1715721496092)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!