Научу вас писать компонент предварительного просмотра урожая vue

GitHub JavaScript

Научу вас писать компонент предварительного просмотра урожая vue

Инструмент для вырезания версии Vue, включая функцию предварительного просмотра

окончательный эффект:Попросите поесть.GitHub.IO/vUE-crop-of…

Адрес источника:GitHub.com/request/v…

Первый шаг: сначала установите скаффолдинг с помощью vue-cli (посмотрите, не установится ли он)официальный сайт vue-cli)

// 初始化vue-cli
vue init webpack my-plugin

Шаг 2: Создайте файл

新建src/views/validSlideDemo.vue,

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

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

Окончательная сгенерированная файловая структура выглядит следующим образом:

vuecorp
Структура файла плагина обрезки

Шаг 3: Зарегистрируйте компонент

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 официальный комментарий к документу установить)
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: '<App/>'
})
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, но это не так, это просто общедоступный метод для нас для создания знаний о плагинах, который можно понимать как метод конструктора в родной 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一样

Шаг 4: Спроектируйте и разработайте свои собственные компоненты и создайте структуру компонентов

Перед этим можно разобраться в соглашениях по именованию компонентов и т.п., можно обратиться к статьеNuggets: спецификации разработки интерфейса Vue, что подробно описано в пункте 2

Во-первых, определите свой собственный метод вызова и параметры, которые необходимо выставить.

<vue-crop
:crop-url="cropUrl1"
:ratio="ratio"
:height="460"
:width="460"
:previewJson="previewJson1"
class="c-crop--preview_right"
@afterCrop="afterCrop"
>
>

Среди них @afterCrop="afterCrop" — функция обратного вызова завершения обрезки, а остальные — конфигурация атрибутов.

В компоненте src/components/VueCrop/VueCrop.vue вы можете использовать this.$emit('afterCrop') для запуска события afterCrop в демоверсии.

С точки зрения структуры компонентов, он в основном делится на: вырезание основной части (VueCrop.vue), компонент выделения (VueCropTool.vue), расчет ширины поля обрезки, координаты положения и другие вычисления (VueCropMove.js), общедоступная регистрация событий перетаскивания. js (компоненты /utils/draggable.js)

Общая идея текущего плагина обрезки
  1. Основная часть подключаемого модуля обрезки состоит из изображений, выделений и структур предварительного просмотра.
  2. Выделение (VueCropTool.vue) отвечает за перетаскивание, чтобы изменить его размер, положение координат и т. д. и вернуть его в VueCrop.vue.
  3. Отображение предварительного просмотра синхронизации основного расчетного значения (c-crop--preview)
  4. Основная часть вызывает событие afterCrop вызывающей страницы (VueCropDemo.vue), тем самым передавая параметры для возврата обрезанного URL-адреса, левого, верхнего, нижнего, правого, x, y, w, h и т. д.

Примечания: В этом компоненте нет функции реальной обрезки.Окончательная обрезка передается фону для обрезки.Если хотите расширить, то можете обработать по координатам и другой информации в функции afterCrop.

Далее мы объясним каждый компонент и js

1. draggable.js основан на элементе, и часть его была изменена.Исходный код выглядит следующим образом
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部分
<script>
import VueCropTool from './VueCropTool.vue'
export default {
  name: 'VueCrop',
  data () {
    return {
      // 根据裁切后的缩放和坐标等生成的预览尺寸坐标数组
      previewImgSize: null,
      // 图片初始数据
      originImgSize: null,
      // 裁切框宽度
      elWidth: 0,
      // 裁切框高度
      elHeight: 0,
      // 裁切框top
      cursorTop: 0,
      // 裁切框left
      cursorLeft: 0,
      // 根据当前的容器宽高计算出的图片尺寸
      imgH: 0,
      imgW: 0,
      // 图片url
      url: this.cropUrl,
      // 为适应当前的容器对原始图片的缩放值
      scale: 1,
      // 根据当前选区和原始图片缩放前的尺寸,来得到最终的裁切尺寸
      coord: null,
      // 计算出的裁切框的初始值
      cropJson: {
        cw: null,
        ch: null,
        w: null,
        h: null,
        r: null
      }
    }
  },
  // 暴露出去的参数,具体解释可看前文的表格
  props: {
    cropUrl: String,
    // 比例
    ratio: {
      type: null,
      default: false
    },
    width: null,
    height: null,
    coordWidth: null,
    coordHeight: null,
    previewJson: {
      type: Array,
      default: function () {
        return []
      }
    }
  },
  components: {
    VueCropTool
  },
  created () {
  },
  watch: {
  	// 监听图片路径变化
    cropUrl (val) {
      this.url = val
      // setTimeout是为了兼容马上获取尺寸获取不到的情况
      setTimeout(() => {
        this.setSize()
      }, 200)
    }
  },
  methods: {
  	 // 更新拖拽尺寸,大部分由裁切框组件通过@updateSize触发
    drapSizeUpdate (w, h, t, l) {
      // 更新裁切框尺寸
      this.elWidth = w
      this.elHeight = h
      this.cursorTop = t
      this.cursorLeft = l
      // 根据当前选区获取原始图片缩放前的尺寸(还原原始图片的宽高以获取最终裁切数据)
      this.coord = this.getCoord(l, t, w, h)
      // 更新预览尺寸
      this.setPreviewSize(this.coord)
    },
    // 裁切完毕回调
    afterCrop () {
      this.$emit('afterCrop', this.coord, this.url)
    },
    // 设置preview尺寸
    setPreviewSize (coord) {
      if (!this.previewJson.length) {
        return false
      }
      let result = this.previewJson.map(data => {
      	 // 计算缩放比
        let scale = data.width / coord.w
        return {
          scale,
          l: -scale * coord.l,
          t: -scale * coord.t,
          w: scale * this.originImgSize.w,
          h: scale * this.originImgSize.h
        }
      })
      this.previewImgSize = result
    },
    // 设置裁切显示的图片尺寸,存储scale值
    async setSize () {
      if (!this.url) {
        return
      }
      let imgSize = await this.getSize(this.url)
      this.originImgSize = imgSize
      this.setCoordRange()
      this.scale = imgSize.w / this.imgW
      this.cursorTop = 0
      this.cursorLeft = 0
      let json = {...this.cropJson}
      json.w = this.imgW
      json.h = this.imgH
      // 有固定比例,则按比例截取
      if (this.ratio) {
        json.r = this.ratio
        if (json.w > json.h) {
          let r = json.h * this.ratio / json.w
          if (r > 1) {
            json.ch = json.h / r
            json.cw = json.ch * this.ratio
          } else {
            json.ch = json.h
            json.cw = json.ch * this.ratio
          }
        } else {
          let r = json.w / this.ratio / json.h
          if (r > 1) {
            json.cw = json.w / r
            json.ch = json.cw / this.ratio
          } else {
            json.cw = json.w
            json.ch = json.cw / this.ratio
          }
        }
      } else {
      	 // 无比例
        json.cw = json.w
        json.ch = json.h
      }
      // 裁切框的尺寸(/2是取一半的值,使裁切框居中并宽度为一半)
      this.elWidth = json.cw / 2
      this.elHeight = json.ch / 2
      this.cursorTop = json.ch / 4
      this.cursorLeft = json.cw / 4
      this.cropJson = {...json}
      this.drapSizeUpdate(this.elWidth, this.elHeight, this.cursorTop, this.cursorLeft)
    },
    // 根据图片原本的尺寸比例和用户传入的尺寸宽高设置当前可显示的区域图片尺寸
    setCoordRange () {
      var size = {...this.originImgSize}
      var ratio1 = this.width / this.height
      var ratio2 = size.r
      if (ratio2 > ratio1) {
        this.imgW = this.width
        this.imgH = this.width / size.r
      } else {
        this.imgH = this.height
        this.imgW = this.height * size.r
      }
    },
    // 获取裁切后的原始坐标宽高(裁切看到的宽高不是原始图片的宽高)
    getCoord (l, t, w, h) {
      l = this.scale * l
      t = this.scale * t
      w = this.scale * w
      h = this.scale * h
      return {
        p0: [l, t],
        p1: [l + w, t],
        p2: [l + w, t + h],
        p3: [l, t + h],
        w: w,
        h: h,
        l: l,
        t: t
      }
    },
    // 获取是src图片的尺寸
    getSize (src) {
      let _this = this
      let img = this.$el.querySelector('#c-crop--hide_img')
      return new Promise(resolve => {
        if (src && img) {
          img.onload = function () {
            const size = _this.getSizeImg(img)
            resolve(size)
          }
          img.src = src
        } else {
          resolve({
            w: 0,
            h: 0,
            r: 0
          })
        }
      })
    },
    // 获取原始图片的真实宽高、比例
    getSizeImg (img) {
      let w = img.width
      let h = img.height
      let r = w === 0 && h === 0 ? 0 : w / h
      return {
        w: w,
        h: h,
        r: r
      }
    }

  },
  mounted () {
    this.setSize()
  }

}

</script>


3. Часть поля обрезки (VueCropTool.vue все ссылки на исходный код)
//script部分

<script>
// 引入拖拽js
import draggable from '../utils/draggable'
// 引入裁切尺寸计算js
import movePos from './VueCropMove'
// 和VueCropMove有关,序号对应相应的操作,这些类名对应裁切框的四条边,四个角,四个边上的中点,拖拽由这12个位置进行
const dragEle = ['.c-crop--drap_eline', '.c-crop--drap_sline', '.c-crop--drap_wline', '.c-crop--drap_nline', '.c-crop--drap_e', '.c-crop--drap_s', '.c-crop--drap_w', '.c-crop--drap_n', '.c-crop--drap_ne', '.c-crop--drap_se', '.c-crop--drap_sw', '.c-crop--drap_nw']

export default {
  data () {
    return {
      width: this.elWidth,
      height: this.elHeight,
      top: this.cursorTop,
      left: this.cursorLeft,
      // 存储拖拽开始坐标(拖拽改变位置时)
      startPos: [0, 0],
      crop: [],
      // 计时器
      cropTimer: null,
      // 存储拖拽开始坐标尺寸(拖拽改变尺寸时)
      startSize: null
    }
  },
  props: ['elWidth', 'elHeight', 'cursorTop', 'cursorLeft', 'cropJson'],
  created () {},
  watch: {
    elWidth (val) {
      this.width = val
    },
    elHeight (val) {
      this.height = val
    },
    cursorTop (val) {
      this.top = val
    },
    cursorLeft (val) {
      this.left = val
    }
  },

  methods: {
    // 拖拽更新位置
    handleDragLocation (event) {
      let x = event.clientX
      let y = event.clientY
      this.left = x - this.startPos[0] + this.left
      this.top = y - this.startPos[1] + this.top
      this.startPos = [x, y]
      this.handleSize()
      // 更新尺寸
      this.$emit('updateSize', this.width, this.height, this.top, this.left)
      clearTimeout(this.cropTimer)
      // setTimeout是为了拖拽完成才调用afterCrop
      this.cropTimer = setTimeout(() => {
        // 调用回调
        this.$emit('afterCrop')
      }, 200)
    },
    // 拖拽改变位置:绑定事件
    dragCallLocation () {
      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)
        }
      })
    },
    // 根据className获取父元素
    getParentElement (p, className) {
      const classNames = p.className
      if (classNames.indexOf(className) === -1) {
        p = p.parentNode
        return this.getParentElement(p, className)
      } else {
        return p
      }
    },
    // 获取拖拽的尺寸
    getDragSize (event) {
      const el = this.$el
      const screen = this.$cropArea.getBoundingClientRect()
      const rect = el.getBoundingClientRect()
      let json = {
        x: event.clientX,
        y: event.clientY,
        t: rect.top,
        b: rect.bottom,
        l: rect.left,
        r: rect.right,
        w: rect.width,
        h: rect.height,
        screen: screen
      }
      json.ratio = json.w / json.h
      return json
    },
    // 拖拽改变大小
    handleDrag (event, i) {
      // 获取坐标
      // console.info('move', i)
      const json = this.getDragSize(event)
      movePos[i](this, json, this.startSize)
      this.handleSize(true)
      this.$emit('updateSize', this.width, this.height, this.top, this.left)
      clearTimeout(this.cropTimer)
      this.cropTimer = setTimeout(() => {
        // 调用回调
        this.$emit('afterCrop')
      }, 200)
    },
    // 拖拽改变大小:绑定事件
    dragCall (i) {
      let target = this.$el.querySelector(dragEle[i])
      draggable(target, {
        start: (event) => {
          // 开始时拖拽框json
          this.startSize = this.getDragSize(event)
        },
        drag: (event) => {
          this.handleDrag(event, i)
        },
        end: (event) => {
          this.handleDrag(event, i)
        }
      })
    },
    // 改变位置大小
    handleSize (isSize) {
      this.left = range2(this.left, this.width, this.cropJson.w)
      this.top = range2(this.top, this.height, this.cropJson.h)
      if (isSize) {
        let d1 = this.cropJson.w - this.left
        let d2 = this.cropJson.h - this.top
        // 按比例裁切
        if (this.cropJson.r) {
          if (d1 < this.width) {
            this.width = d1
            this.height = this.width / this.cropJson.r
          } else if (d2 < this.height) {
            this.height = d2
            this.width = this.height * this.cropJson.r
          }
        } else {
          // 不按比例裁切
          if (d1 < this.width) {
            this.width = d1
          }
          if (d2 < this.height) {
            this.height = d2
          }
        }
      }
    }

  },
  mounted () {
    this.$cropArea = this.getParentElement(this.$el.parentNode, 'c-crop--area')
    // 初始化拖拽改变大小
    for (var i = 0; i < dragEle.length; i++) {
      this.dragCall(i)
    }
    // 初始化拖拽改变位置
    this.dragCallLocation()
  }
}

// 计算允许的范围
function range2 (pos, val, mainW) {
  return pos <= 0 ? 0 : pos > mainW - val ? mainW - val : pos
}

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