资讯专栏INFORMATION COLUMN

手把手教你写vue裁切预览组件

FreeZinG / 665人阅读

摘要:版本裁切工具,包含预览功能最终效果源码地址第一步先用安装脚手架不会安装的看官网初始化第二步创建文件新建里新建,在配置访问路由具体看源码最终生成的文件结构如下图第三步注册组件引用所有插件导入插件入口文件如果已安装就跳过注册插件全

vue版本裁切工具,包含预览功能

最终效果: https://qiuyaofan.github.io/vue-crop-demo/

源码地址: https://github.com/qiuyaofan/vue-crop

第一步:先用vue-cli安装脚手架(不会安装的看 vue-cli官网)
// 初始化vue-cli
vue init webpack my-plugin
第二步:创建文件
新建src/views/validSlideDemo.vue,

src/components里新建VueCrop/index.js,VueCrop.vue,

在routes/index.js配置访问路由(具体看github源码)

最终生成的文件结构如下图:

第三步:注册组件
1.引用所有插件:src/components/index.js
// 导入插件入口文件
import VueCrop from "./VueCrop/index.js"
const install = function (Vue, opts = {}) {
  /* 如果已安装就跳过 */
  if (install.installed) return
  
  // 注册插件
  Vue.component(VueCrop.name, VueCrop)
}

// 全局情况下注册插件
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue)
}

export {
  install,
  // 此处是为了兼容在vue内多带带引入这个插件,如果是main.js全局引入就可以去掉
  VueCrop
}
2.全局调用插件:src/main.js ( vue plugins官方文档解说install)
import Vue from "vue"
import App from "./App"
import router from "./router"

// 新加的:导入入口文件
import { install } from "src/components/index.js"

// 全局调用,相当于调用 `MyPlugin.install(Vue)`
Vue.use(install)

Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
  el: "#app",
  router,
  components: { App },
  template: ""
})
3.VueCrop入口文件调用VueCrop.vue:src/components/VueCrop/index.js
// 导入vue
import VueCrop from "./VueCrop.vue"

// Vue.js 的插件应当有一个公开方法 install 。这个方法的第一个参数是 Vue 构造器
VueCrop.install = function (Vue) {
  // 注册组件
  Vue.component(VueCrop.name, VueCrop)
}

export default VueCrop
小结:我一开始一直有个误解,以为myPlugin.install是vue的一个方法,其实不是,他只是我们构造plugin识的一个公开方法,可以理解为原生js中的构造函数的方法:
function MyPlugin(){
  console.info("构造函数")
}
MyPlugin.prototype.install=function(vue,options){
    console.info("构造器vue:"+vue);
}

而真正注册组件的是:Vue.component()

所以,vue插件注册的过程是:

1.调用main.js中:
import { install } from "src/components/index.js"
vue.use(install)

2.index.js添加install方法,调用Vue.component注册组件

3.组件内的index.js同所有组件的index.js一样
第四步:设计开发自己的组件,构建组件结构
在此之前,可以先了解下组件的命名规范等,可参考文章 掘金:Vue前端开发规范,其中第2点有详细讲解

首先,确定自己的调用方式和需要暴露的参数


>

其中,@afterCrop="afterCrop"是裁切完成的回调函数,其他是属性配置

在组件src/components/VueCrop/VueCrop.vue内,可以用this.$emit("afterCrop")触发demo里的afterCrop事件

组件结构上,主要分为:裁切主体部分(VueCrop.vue),选框组件(VueCropTool.vue),裁切框宽度、位置坐标等计算(VueCropMove.js),拖拽事件注册公共js(components/utils/draggable.js)

当前裁切插件的总体思路

裁切插件的裁切主体由图片,选框,预览结构组成

选框(VueCropTool.vue)负责拖拽改变其大小,坐标位置等并返回给VueCrop.vue

主体计算数值同步预览显示(c-crop--preview)

主体触发调用页面(VueCropDemo.vue)的afterCrop事件,从而传递参数返回裁切后的url,left,top,bottom,right,x,y,w,h等

备注:此组件不具备真实的裁切功能,最终的裁切是传递给后台去裁,你如果想扩展可以在afterCrop函数里根据坐标等信息进行处理

接下来我们对各个组件和js进行讲解

1.draggable.js是参照element里的,修改了一部分,源码如下
export default function (element, options) {
  const moveFn = function (event) {
    if (options.drag) {
      options.drag(event)
    }
  }
  // mousedown fn
  const downFn = function (event) {
    if (options.start) {
        // 调用参数中start函数
      options.start(event)
    }
  }
  // mouseup fn
  const upFn = function (event) {
    document.removeEventListener("mousemove", moveFn)
    document.removeEventListener("mouseup", upFn)
    document.onselectstart = null
    document.ondragstart = null

    if (options.end) {
        // 调用参数中end函数
      options.end(event)
    }
  }
  // 绑定事件
  element.addEventListener("mousedown", event => {
    if (options.stop && options.stop(event, element) === false) {
      return false
    }
    document.onselectstart = function () {
      return false
    }
    document.ondragstart = function () {
      return false
    }
    document.addEventListener("mousedown", downFn)
    document.addEventListener("mousemove", moveFn)
    document.addEventListener("mouseup", upFn)
  })
}
VueCropTool.vue使用如下
draggable(this.$el.querySelector(".c-crop--drap_screen"), {
    start: (event) => {
      this.startPos = [event.x, event.y]
    },
    drag: (event) => {
      this.handleDragLocation(event)
    },
    end: (event) => {
      this.handleDragLocation(event)
    }
})
2.裁切主体部分(VueCrop.vue全部源码链接)
//script部分


3.裁切框部分(VueCropTool.vue全部源码链接)
//script部分

4.计算裁切框的js(VueCropMove.js全部源码链接)
// 12种形态,四条边,边的中点,边的四个角。e:东,w:西,n:北,s:南,ne:东南以此类推
const movePos = {
  0: e,
  4: e,
  1: s,
  5: s,
  2: w,
  6: w,
  3: n,
  7: n,
  8: ne,
  9: se,
  10: sw,
  11: nw
}
let width, height, result, ratio

// 获取某种形态类型的宽或高最大值
function getMaxSize (json, startJson, dire, type) {
  if (type === "w") {
    switch (dire) {
      case "e":
      case "s":
      case "n":
      case "ne":
      case "se":
        return json.screen.right - json.l
      case "w":
      case "nw":
      case "sw":
        return startJson.r - json.screen.left
    }
  } else if (type === "h") {
    switch (dire) {
      case "n":
      case "nw":
      case "ne":
        return startJson.b - json.screen.top
      case "s":
      case "w":
      case "e":
      case "sw":
      case "se":
        return json.screen.bottom - startJson.t
    }
  }
}
// 判断是否有ratio,返回修改后的尺寸
function setRatioSize (type, json, startJson, ratio, width, height) {
  if (ratio) {
    if (width / ratio >= height) {
      var maxHeight = getMaxSize(json, startJson, type, "h")
      height = width / ratio
      if (height > maxHeight) {
        height = maxHeight
        width = height * ratio
      }
    } else {
      var maxWidth = getMaxSize(json, startJson, type, "w")
      width = height * ratio
      if (width > maxWidth) {
        width = maxWidth
        height = width / ratio
      }
    }
  }
  return {
    width: width,
    height: height
  }
}
// 拖拽东边,高度是不变的,除非有比例拖拽时
function e (_this, json, startJson) {
  ratio = _this.cropJson.r
  width = range(getWidth(json, startJson, "e"), getMaxSize(json, startJson, "e", "w"))
  if (ratio) {
      // 有比例时,计算高度,并对比最大值是否超出
    height = range(width / ratio, getMaxSize(json, startJson, "e", "h"))
    result = setRatioSize("e", json, startJson, ratio, width, height)
    setSize(_this, result)
  } else {
    _this.width = width
  }
  return _this
}

// 拖拽南边,宽度是不变的,除非有比例拖拽时
function s (_this, json, startJson) {
  ratio = _this.cropJson.r
  height = range(getHeight(json, startJson, "s"), getMaxSize(json, startJson, "s", "h"))
  if (ratio) {
    // 有比例时,计算宽度,并对比最大值是否超出
    width = range(height * ratio, getMaxSize(json, startJson, "s", "w"))
    result = setRatioSize("s", json, startJson, ratio, width, height)
    setSize(_this, result)
  } else {
    _this.height = height
  }

  return _this
}

// 以下同上,以此类推
function w (_this, json, startJson) {
  ratio = _this.cropJson.r
  width = range(getWidth(json, startJson, "w"), getMaxSize(json, startJson, "w", "w"))
  if (ratio) {
    height = range(width / ratio, getMaxSize(json, startJson, "w", "h"))
    result = setRatioSize("w", json, startJson, ratio, width, height)
    setSize(_this, result)
    _this.left = getLeft(_this, json, startJson)
  } else {
    _this.width = width
    _this.left = rangeMax(json.x - json.screen.left, startJson.r)
  }
  return _this
}
function n (_this, json, startJson) {
  ratio = _this.cropJson.r
  height = range(getHeight(json, startJson, "n"), getMaxSize(json, startJson, "n", "h"))
  if (ratio) {
    width = range(height * ratio, getMaxSize(json, startJson, "n", "w"))
    result = setRatioSize("n", json, startJson, ratio, width, height)
    setSize(_this, result)
    _this.top = getTop(_this, json, startJson)
  } else {
    _this.height = height
    _this.top = rangeMax(json.y - json.screen.top, startJson.b)
  }
  return _this
}

function ne (_this, json, startJson) {
  height = range(getHeight(json, startJson, "n"), getMaxSize(json, startJson, "ne", "h"))
  width = range(getWidth(json, startJson, "e"), getMaxSize(json, startJson, "ne", "w"))
  result = setRatioSize("ne", json, startJson, _this.cropJson.r, width, height)
  setSize(_this, result)
  _this.top = getTop(_this, json, startJson)
  return _this
}
function se (_this, json, startJson) {
  height = range(getHeight(json, startJson, "s"), getMaxSize(json, startJson, "se", "h"))
  width = range(getWidth(json, startJson, "e"), getMaxSize(json, startJson, "se", "w"))
  result = setRatioSize("se", json, startJson, _this.cropJson.r, width, height)
  setSize(_this, result)
  return _this
}
function sw (_this, json, startJson) {
  width = range(getWidth(json, startJson, "w"), getMaxSize(json, startJson, "sw", "w"))
  height = range(getHeight(json, startJson, "s"), getMaxSize(json, startJson, "sw", "h"))
  result = setRatioSize("sw", json, startJson, _this.cropJson.r, width, height)
  setSize(_this, result)
  _this.left = getLeft(_this, json, startJson)
  return _this
}
function nw (_this, json, startJson) {
  width = range(getWidth(json, startJson, "w"), getMaxSize(json, startJson, "nw", "w"))
  height = range(getHeight(json, startJson, "n"), getMaxSize(json, startJson, "nw", "h"))
  result = setRatioSize("nw", json, startJson, _this.cropJson.r, width, height)
  setSize(_this, result)
  _this.left = getLeft(_this, json, startJson)
  _this.top = getTop(_this, json, startJson)
  return _this
}

// 匹配范围
function range (value, max) {
  value = value > max ? max : value
  return value < 20 ? 20 : value
}
// 最大值
function rangeMax (value, max) {
  return value > max ? max : value
}
// top
function getTop (_this, json, startJson) {
  return rangeMax(startJson.b - _this.height - json.screen.top, startJson.b)
}
// left
function getLeft (_this, json, startJson) {
  return rangeMax(startJson.r - _this.width - json.screen.left, startJson.r)
}
// height:只存在于s||n类型
function getHeight (json, startJson, type) {
  return type === "n" ? startJson.b - json.y : json.y - startJson.t
}
// width:只存在于w||e类型
function getWidth (json, startJson, type) {
  return type === "w" ? startJson.r - json.x : json.x - startJson.l
}
// setSize
function setSize (_this, result) {
  _this.width = result.width
  _this.height = result.height
}

export default movePos

今天就分享到这里啦~喜欢这个插件可以去 github star~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94799.html

相关文章

  • 把手你写 Vue UI 组件库@vue2.0

    摘要:手把手教你写组件库最近在研究的实现,发现网上很少有关于插件具体实现的文章,官方的文档也只是一笔带过,对于新手来说并不算友好。 手把手教你写 Vue UI 组件库 最近在研究 muse-ui 的实现,发现网上很少有关于 vue 插件具体实现的文章,官方的文档也只是一笔带过,对于新手来说并不算友好。 笔者结合官方文档,与自己的摸索总结,以最简单的 FlexBox 组件为例子,带大家入门 v...

    Keagan 评论0 收藏0
  • 把手教你用jsx封装Vue中的复杂组件(网易云音乐实战项目需求)

    摘要:终极解决方案所以我们要统一环境,直接使用渲染我们的组件,文档可以参照音乐标题歌手专辑时长省去一些细节注意需要放在中,的透传也不要忘了,这样我们在外部想使用的一些属性和事件才比较方便。 背景介绍 最近在做vue高仿网易云音乐的项目,在做的过程中发现音乐表格这个组件会被非常多的地方复用,而且需求比较复杂的和灵活。 预览地址 源码地址 图片预览 歌单详情 showImg(https://se...

    HitenDev 评论0 收藏0
  • 把手教你撸个vue2.0弹窗组件

    摘要:组件结构同组件结构通过方法获取元素的大小及其相对于视口的位置,之后对提示信息进行定位。可以用来进行一些复杂带校验的弹窗信息展示,也可以只用于简单信息的展示。可以通过属性来显示任意标题,通过属性来修改显示区域的宽度。 手把手教你撸个vue2.0弹窗组件 在开始之前需要了解一下开发vue插件的前置知识,推荐先看一下vue官网的插件介绍 预览地址 http://haogewudi.me/k...

    mrli2016 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<