目的:
1.任何元素,只需要插入v-drag即可拖动,
2.可根据自定义属性控制结束拖动后是否吸附最近的墙面(屏幕边界)
3.可根据自定义属性来判断pc与移动端,分别执行mouse事件和touch事件
首先声明一个对象,并拥有install方法,然后将其暴露出去。不多解释,官方文档已经很明确了。
const VDrag = {}
VDrag.install = function (Vue) {
}
export default VDrag
Vue参数是被use调用时传入Vue实例,我们的目标是通过自定义指令实现,于是只需要在vue上添加一条自定义指令即可
const VDrag = {}
VDrag.install = function (Vue) {
Vue.directive('drag', function (el,binding,vnode){
})
}
export default VDrag
其中,我们需要通过el来获取元素的一些信息,如元素当前位置等;通过binding获取自定义指令的传值(目前这个插件没用到);通过vnode获取组件上的自定义属性及各种on事件。
let element = el
element.setAttribute('draggable',false)
element.style.userDrag = 'none'
// 正则筛出原本带有的样式,以便之后拼接
let oldStyle = element.style.cssText.replace(/^(position|left|top):\w+;&/g,'')
let newStyle
const onmousemove=(e)=>{
newStyle = oldStyle + `position:absolute;left:${e.x - element.clientWidth/2 + 'px'};top:${e.y - element.clientHeight/2 + 'px'}`
element.style.cssText = newStyle
}
element.onmousedown=()=>{
element.addEventListener('mousemove', onmousemove)
}
element.onmouseup=(()=>{
element.removeEventListener('mousemove',onmousemove)
if(securityObject(vnode.data,'attrs.stick-wall')){
stickWall(element)
}
if(securityObject(vnode.data,'on.dragChange')){
// vnode.data.on.change(newStyle.match(/(?<=left:).*?(?=;)/g))
vnode.data.on.dragChange({
x: element.style.left,
y: element.style.top
})
}
以上代码大概实现了基本的拖拽,并且根据自定义属性stick-wall判断是否贴墙操作,并执行dragChange事件,将xy轴信息传入。
其中stickWall与securityObject方法被我提取成了公用方法,代码如下:
// 吸墙计算
const stickWall = (element) =>{
// body宽度
let bodyWidth = window.innerWidth
// body高度
let bodyHeight = window.innerHeight
// 拖动元素宽度
let elWidth = element.clientWidth
// 拖动元素高度
let elHeight = element.clientHeight
// 拖动元素距离左边距离
let elLeft = parseInt(element.style.left)
// 拖动元素距离上边距离
let elTop = parseInt(element.style.top)
// 距屏幕边的距离,上右下左
let distance = [
{
name:'top',
value: elTop
},
{
name: 'right',
value: bodyWidth-elWidth-elLeft
},
{
name: 'bottom',
value: bodyHeight-elTop-elHeight
},
{
name: 'left',
value: elLeft
}
]
// 求出最小值
let minNumber = Math.min.apply(null,distance.map(item=> item.value))
// 根据最小值找到对应方向
let minDirection = distance.find(item => item.value === minNumber).name
// 各个方向赋值字典
let directionMap = {
top: 0,
right: bodyWidth - elWidth,
bottom: bodyHeight - elHeight,
left: 0
}
// bottom与right转为top与left
let transferDirection = {
top: 'top',
right: 'left',
bottom: 'top',
left: 'left'
}
// 元素最终赋值
element.style[transferDirection[minDirection]] = directionMap[minDirection] + 'px'
}
// 安全获取对象属性
const securityObject = (obj,attr)=>{
let array = attr.split('.')
let value = obj
array.forEach((item)=>{
if(value){
value = value[item]
} else {
value = undefined
}
})
return value
}
export {stickWall,securityObject}
至此,这个组件算是完成了。只需要引入即可使用了
//main.js
import VDrag from '插件路径'
Vue.use(VDrag)
//template
<div v-drag :stick-wall="true" style="width: 500px;height: 500px;background-color: yellow;position: absolute">
</div>
接下来准备发布插件,首先将代码打包成一个js文件(我这脚手架用的是简化版cli——webpack-simple);更改webpack.config.js
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './src/lib/drag.js', //打包的入口文件
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/', //打包后输出文件夹
filename: 'vue-drag.js', //打包后文件名
library: 'VDrag', // library指定的就是你使用require时的模块名,这里便是require("VDrag")
libraryTarget: 'umd', //libraryTarget会生成不同umd的代码,可以只是commonjs标准的,也可以是指amd标准的,也可以只是通过script标签引入的。
umdNamedDefine: true // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define。
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
// other vue-loader options go here
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json']
},
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
执行npm run build,得到打包后的js
接下来修改package.json,主要是设置一些发布信息,如作者信息、版本号、是否私人等,以下关键代码:
"name": "vue-drag-wkq", //不能重名,否无法发布为公有插件
"description": "基于vue的拖拽指令,只需标签中加入v-drag即可实现拖拽",
"version": "1.0.116", // 版本号,每次发布插件时都需要有变化,否则无法发布
"author": "wangkaiqiang001 <1426422145@qq.com>",
"license": "MIT",
"private": false, // 为false才可以让其他开发者通过npm下载使用
"main": "dist/vue-drag.js", //指定被引入时操作的文件(import xxx from 'vue-drag-wkq')
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},
然后登陆npm账号:
npm login
输入帐密,登陆;
执行npm publish;
成功(失败一般是名称重复或者版本号忘记改)
然后可以愉快的通过npm install下载自己的的插件了
ps:其中有一点很疑惑,就是打包过后本地引用然后use是不可以的,但是发布到npm在use却可以。按我理解两者这两者应该没差,就算发布到npm,我package.json中main的指向也是该文件。希望知道为什么的朋友可以告诉下是什么原理^_^