UNPKG

miniprogram-image-picker

Version:

customized image picker component for wechet MiniProgram,小程序自定义图片选择组件

485 lines (440 loc) 16.3 kB
import { array_move } from 'polyfill.js'; import { throttle } from './polyfill'; /** * 临时数据缓存 */ // const Cache = { // /** // * @type {{path:String,size:number}[]} // */ // imgs: [],// 当前的图片列表[{path,size}] // originId: -1, // originIndex: -1,//记录每次移动的其实位置 // lastTargetIndex: -1,//记录移动的上一次位置 // /** // * @type {{x:number,y:number}} // */ // lastPostion: {},//记录上次位置 // // showTips: true, //是否显示提示通知,首次添加图片时显示通知 // } Component({ properties: { value: { type: Array, value: [], desc: "用户选择的照片列表", observer: function (newVal, oldVal) { //不能直接判断是否相等 if (newVal && JSON.stringify(oldVal) != JSON.stringify(newVal)) { this._getCache().imgs = []; this.data.imgList = []; this._add(newVal, true); } } }, column: { type: Number, value: 3, desc: "列数2~5,默认3", observer: function (newVal, oldVal) { //column 重新渲染大小和位置 if (this.isReady && newVal != oldVal) { this.properties.column = newVal; // const updateData = this._calcPostion(0, Cache.imgs.length); // this._updateAsync(updateData); this._renderAllItems(); } } }, max: { type: Number, value: 9, desc: "最多图片数量,默认9", observer: function (newVal, oldVal) { //重新渲染加号 if (this.isReady && newVal != oldVal) { this.properties.max = newVal; this._renderIcon({}, this.cache.imgs.length); } } }, }, data: { imgList: [],//显示的图像列表 length: 120,//每张图片的边长 iconX: 0, //选择图像按钮横坐标 iconY: 0, //选择图像按钮纵坐标 animation: false, //是否启用动画 movable: false, }, options: { // addGlobalClass: true, // 允许全局样式覆盖 }, behaviors: [ 'wx://form-field', //表单特性,可作为表单一部分,提交时直接获取列表 // 'wx://component-export' //select 返回值 2.2.3开始支持 ], export() { // 'wx://component-export' //select 返回值 2.2.3开始支持 return { value: this._getCache().imgs } }, created(){ this.onChange = throttle(this.onChange); }, /** * 初始化缓存 * 渲染全部图标 */ ready() { this._getCache();//init cache this._renderAllItems(); this.isReady = true; }, methods: { /** * @returns {{imgs:[],originId:number,originIndex:number,lastTargetIndex:number,lastPostion:{x:number,y:number}}} */ _getCache() { return this.cache || (this.cache = { /** * @type {{path:String,size:number}[]} */ imgs: [],// 当前的图片列表[{path,size}] originId: -1, originIndex: -1,//记录每次移动的其实位置 lastTargetIndex: -1,//记录移动的上一次位置 /** * @type {{x:number,y:number}} */ lastPostion: {},//记录上次位置 }) }, /** * 选图事件 */ onChooseImage() { wx.chooseImage({ count: this.properties.max - this._getCache().imgs.length, sizeType: this.dataset.type || ['compressed', 'original'], sourceType: this.dataset.source || ['album', 'camera'], success: (res) => { console.debug('choose', res.tempFiles); this._add(res.tempFiles); }, fail: (err) => { console.error(err); } }); }, /** * 发生触摸事件 * @param {Event} e */ onTouchStart(e) { const id = e.currentTarget.dataset.id; this.cache.originId = id; this.cache.originIndex = this._findValueIndexByImgListId(id); }, /** * 触摸操作结束 * @param {Event} e */ onTouchEnd(e) { // console.debug('end', e, 'last', Cache.lastTargetIndex) const cache = this._getCache(); const originId = cache.originIndex; if (originId < 0) { // 未触发 start return; } const moving = this.data.imgList[e.currentTarget.dataset.id].moving; if (moving) { const lastindex = cache.lastTargetIndex; console.info('move end to', lastindex); if (lastindex != originId) { this._triggerInput(cache.imgs, 'move', { from: originId, to: lastindex }); } const updateData = this._calcPostion(0, cache.imgs.length); updateData['movable'] = false; this._updateAsync(updateData); } cache.originIndex = cache.lastTargetIndex = -1; }, /** * 出发移动事件 * Touchend 事件触发后还会触发数次change事件 * 此次通过检查状态判断是否处理 * @param {*} e */ onChange(e) { const detail = e.detail; const id = e.currentTarget.dataset.id; const cache = this._getCache(); // console.debug('c', id, Cache.originId, detail) if (detail.source < 0 || cache.originId !== id) { // 非手动触发 return } const originIndex = cache.originIndex; const index = this._findValueIndexByImgListId(id); if (originIndex < 0) { // touchend结束已触发 // 元素归位 this._updateAsync({ [`imgList[${id}].moving`]: false, [`imgList[${id}].x`]: index % this.properties.column * this.data.length, [`imgList[${id}].y`]: Math.floor(index / this.properties.column) * this.data.length, }) return; } if (cache.lastTargetIndex === -1) { // 首次发生变化 // move 状态置 true cache.lastTargetIndex = index; this._updateAsync({ [`imgList[${id}].moving`]: true, [`imgList[${id}].x`]: detail.x, [`imgList[${id}].y`]: detail.y, movable: true }); return; } const x = detail.x; const y = detail.y; const delta = this.data.length / 6; if (Math.abs(cache.lastPostion.x - x) < delta && Math.abs(cache.lastPostion.y - y) < delta) { //计算移动距离小于 delta 时不处理 return } cache.lastPostion = { x, y } const newTargetIndex = this._getTargetIndex(x, y); if (newTargetIndex >= 0 && newTargetIndex !== cache.lastTargetIndex) { // 计算新坐标,异步刷新 const data = this._move(cache.lastTargetIndex, newTargetIndex); data[`imgList[${id}].x`] = x; data[`imgList[${id}].y`] = y; data[`imgList[${id}].index`] = newTargetIndex; this._updateAsync(data); cache.lastTargetIndex = newTargetIndex; } }, /** * 删除 * @param {Event} e */ onDelete(e) { const id = e.currentTarget.dataset.id; this._delete(id); }, /** * 大图预览图片 * @param {Event} e */ onPreview(e) { const id = e.currentTarget.dataset.id; const urls = this.cache.imgs.map(f => f.path); wx.previewImage({ current: urls[this._findValueIndexByImgListId(id)], urls: urls }); }, /** * 重新渲染图片和图标 */ resize(data) { const size = this.data.imgList.length; const updateData = this._calcPostion(0, size); if (data) { for (let key in data) { updateData[key] = data[key]; } } this._renderIcon(updateData, size); }, /** * 重新计算图像宽度 */ _renderAllItems() { wx.createSelectorQuery() .in(this) .select('.Picker') .boundingClientRect(res => { const length = ((res.width > 15) ? res.width : wx.getSystemInfoSync().windowWidth) / this.properties.column; // 计算每张图的边长 if (this.properties.value) { this.data.length = length; setTimeout(() => this.resize({ length }), 100);//defer 防止首次加载长度为0 } else { this.setData({ length }); } }).exec(); }, /** * 移动交换 * @param {int} start * @param {int} end * @returns {object} */ _move(start, end) { console.debug('move from:', start, 'to:', end); array_move(this.cache.imgs, start, end); return this._calcPostion(start, end); }, /** * 添加图片 * * @param {array} fileList */ _add(fileList, noEvent) { const value = this.cache.imgs; let len = value.length; Array.prototype.push.apply(value, fileList);//merge if (!noEvent) { this._triggerInput(value, 'add', fileList); } const length = this.data.length; const col = this.properties.column; const updateData = {}; fileList.forEach(img => { updateData[`imgList[${len}]`] = { img, index: len, x: (len % col) * length, y: Math.floor(len / col) * length, } ++len; }); this._renderIcon(updateData, len); }, /** * 同步删除索引 * @param {int} index */ _delete(id) { console.info('delete', id); const imgList = this.data.imgList; const value = this.cache.imgs; let value_index = this._findValueIndexByImgListId(id); value.splice(value_index, 1); this._triggerInput(value, 'delete', { index: value_index }); imgList.splice(id, 1); const col = this.properties.column; const length = this.data.length; const to = value.length; while (value_index < to) { const item = imgList.find(e => e.img == value[value_index]); item.x = (value_index % col) * length; item.y = Math.floor(value_index / col) * length; item.index = value_index; ++value_index; } this.setData({ imgList, iconX: Math.floor(to % col) * length, iconY: Math.floor(to / col) * length }); }, /** * 触发input事件 * @param {Array} value * @param {String} type * @param {Object} detail */ _triggerInput(value, type, detail) { console.debug('new value', value); this.properties.value = value; this.triggerEvent("input", { value, type, }); this.triggerEvent(type, detail); }, /** * * @param {number} x * @param {number} y */ _getTargetIndex(x, y) { const length = this.data.length; if (x < 0) { x = 1 }; if (y < 0) { y = 1 }; let pointX = x + length / 2; let pointY = y + length / 2; const col = this.properties.column; const AreaWidth = length * col; if (pointX > AreaWidth) { pointX = AreaWidth - 1; } if (pointY > this.data.iconY + length) { pointY = this.data.iconY + length / 2; } let n = this.cache.imgs.length; while (n--) { const X = (n % col) * length; const Y = Math.floor(n / col) * length; if (X < pointX && pointX < X + length && Y < pointY && pointY < Y + length) { return n; } } return -1; }, /** * 根据imglist ID反查显示图像中的索引顺序 * @param {number} id */ _findValueIndexByImgListId(id) { return this.cache.imgs.indexOf(this.data.imgList[id].img); }, /** * * @param {number} start * @param {number} end */ _calcPostion(start, end) { const step = start < end ? 1 : -1; const imgList = this.data.imgList; const value = this.cache.imgs; const col = this.properties.column; const length = this.data.length; const updateData = {}; for (let i = start; i != end; i += step) { const id = imgList.findIndex(e => e.img == value[i]); if (id < 0) { console.error('img not found:', i, value[i]); continue; } updateData[`imgList[${id}].index`] = i; updateData[`imgList[${id}].x`] = (i % col) * length; updateData[`imgList[${id}].y`] = Math.floor(i / col) * length; } return updateData; }, _renderIcon(updateData, len) { len = len || this.data.imgList.length; const col = this.properties.column; const max = this.properties.max; const length = this.data.length; if (len < max) { updateData['iconX'] = (len % col) * length; updateData['iconY'] = Math.floor(len / col) * length; } else { // 达到最大值隐藏图标 updateData['iconX'] = null; updateData['iconY'] = Math.floor((max - 1) / col) * length; } updateData['animation'] = false; this._updateAsync(updateData); }, /** * 异步更新数据 * @param {object|string} data * @param {any} value */ _updateAsync(data, value) { if (2 == arguments.length) { data = { [data]: value }; } wx.nextTick(() => { // console.debug('update async', data); this.setData(data); }) } } });