赞
踩
webp是image组件的一个boolean属性,开启这个属性之后,代表url可以设置webp这种格式的图片。 webP是一种同时提供了有损压缩与无损压缩的,并且是可逆的图片压缩的这种文件格式,这种文件 格式是由谷歌推出的。 image组件模式是不解析 webP这种图片格式的,它只支持网络图片资源,只有开启了webp属性之后, 才可以解析 webP这种图片网址。 那么我们为什么要使用 webP这种图片格式呢?它有什么优势呢? webP的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,并且拥有肉眼识别无差异的图像质量, 同时它还提供了无损以及有损这两种图片压缩模式,还提供了alpha透明,以及动画的特性,对JPEG 、PNG等这些图片格式 的转化都有支持。并且转化的效果都是相当优秀的。 webP既可以替代JPEG、PNG这些静态的图片,它也可以替代GIF这种动态图片。 转化 webP的格式的方法 智图:https://zhitu.isux.us/ 在线转化网址 将普通的JPEG、GIF、PNG的图片,转化成一个 webP的格式 转化后的webP格式的图片,直接可以用谷歌浏览器打开
show-menu-by-longpress是image组件的一个boolean属性,开启它的时候就是代表开启了长按图片,
显示识别小程序码的一个菜单
但是它这种识别是在wx.previewImage接口调用的时候,开始预览图片的时候,才可以识别
实现的效果
image组件的lazy-load属性,它规定图片即将进入一定范围之内的时候,
这个范围是上、下三屏,才开始去加载图片。
上下三屏其实是一个很大的范围空间,一般情况下会导致我们的图片,图片在页面加载的时候,第一次就加载了几十张图片。
实际体验
刚进入页面不进行任何操作 – 33个请求
开启lazy-load属性的image组件,在我们使用以后,第一页,什么也不操作,图片请求了33次,实际加载32次。
并且image组件提供的图片懒加载功能,它只支持针对 page 与 scroll-view 下面的image组件有效
往上滚动之后,有新的图片加载进来
官方mage组件的lazy-load属性,是有效果的,是分上下三屏,在我们往上滚动的时候,有新的图片加载进来
https://developers.weixin.qq.com/community/develop/article/doc/0002e4522b0ef8f1b1b992b0151813
刚进入页面不进行任何操作 – 11个请求
不是官方image组件一下子加载很多的情况
往上滚动,会有新的加载
从效果上看,mina-lazy-image 自定义图片懒加载组件,它的确是优于image组件。 主要原理是,使用一个wx.createIntersectionObserver这个接口, 使用接口创建了一个IntersectionObserver的实例, IntersectionObserver交叉监测,用这个实例去判断图片是否出现在用户的视图窗口中, 如果出现了,再进行加载。 这个实例有4个方法: relativeTo(string selector,Object margins) 使用选择器指定一个组件节点作为参照区域 这个选择器可以是id选择器,也可以是类选择器 relativeToViewport(Object margins) 它指定页面的视图显示区域,作为交叉判断的参照区域 第二个方法和第一个方法的区别,在于它指定参考的对象是不一样的, 参数是一个对象,这个对象描述视图窗口的边界,共有四个字段 left number 区域左边界 right number 区域右边界 top number 区域上边界 bottom number 区域下边界 4个参数可以不全部指定 observe(string targetSelector,callback) 用选择器指定目标节点,并且开始监听交叉状态的一个变化情况 变化情况会在callback回调函数中去返回 disconnect() 监听完成,要停止监听
源码
miniprogram_npm/mina-lazy-image/index.wxml
<view class="lazy-image-comp image-container-class">
<!-- src 高清图 -->
<image wx:if="{{showed}}" style="{{styles}}" class="final-image image-class" src="{{src}}" mode="{{mode}}" webp="{{webp}}" show-menu-by-longpress="{{showMenuByLongpress}}" bindload="onLoad" binderror="onError" />
<!-- placeholder 缩略图 -->
<image wx:else="{{placeholder}}" style="{{styles}}" src="{{placeholder}}" mode="{{mode}}" webp="{{webp}}" class="preview-image image-class" />
</view>
miniprogram_npm/mina-lazy-image/index.js
module.exports = /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; // 是否支持新接口 // 使用连个!,可以将undefined以及null这样的值,快速安全的转化为一个boolean值 var supportObserver = !!wx.createIntersectionObserver; // 组件 Component({ data: { showed: false, errorImage: '' }, externalClasses: ['image-class', 'image-container-class'], // ready 在组件在视图层布局完成后执行 ready: function ready() { this.addObserver(); }, // detached 在组件实例被从页面节点树移除时执行 detached: function detached() { this.clean(); }, // 属性对象 properties: { src: { type: String, value: '' }, placeholder: { type: String, value: '' }, mode: { type: String, value: 'scaleToFill' }, webp: { type: Boolean, value: false }, showMenuByLongpress: { type: Boolean, value: false }, styles: { type: String, value: '' }, viewport: { type: Object, value: { bottom: 0 } } }, methods: { clean: function clean() { if (this.observer) { // disconnect 移除监听 提高代码的运行效率 // 再不需要监听的时候,及时将监听断掉 this.observer.disconnect(); } this.observer = null; }, onError: function onError(e) { this.triggerEvent('error', { detail: e.detail }); }, onLoad: function onLoad(e) { this.triggerEvent('load', { detail: e.detail }); }, addObserver: function addObserver() { var _this = this; // 是否支持新接口 if (!supportObserver) { return this.setData({ showed: true }); } // 如果observer对象已经创建,就返回不要重复创建 // 滚动的之后,可能会上下来回滚动,来回进入视图区域,所以这个时候我们要避免重复的去创建 if (this.observer) { return false; } try { /* 创建IntersectionObserver实例 组件内 this.createIntersectionObserver(); 组件外 wx.createIntersectionObserver(); */ var observer = this.createIntersectionObserver(); /* relativeToViewport 将参考区域绑定到 bottom: 0 的视图窗口上面 viewport 组件的属性对象里面定义的一个属性值 properties: { viewport: { type: Object, value: { bottom: 0 } } }, bottom: 0 从页面的底部算起 observe 开始监听 .lazy-image-comp 是自定义组件的一个顶层样式名称 选择器,回调函数 */ observer.relativeToViewport(this.properties.viewport).observe('.lazy-image-comp', function () { // 将showed 设置为true _this.setData({ showed: true }); // 清理 _this.clean(); }); this.observer = observer; return true; } catch (e) { this.setData({ showed: true }); return false; } } } }); /***/ }) /******/ ]);
miniprogram_npm/mina-lazy-image/index.json
{
"component": true,
"usingComponents": {}
}
miniprogram_npm/mina-lazy-image/index.wxss
@keyframes animateShow { 0% { opacity: 0; } 100% { opacity: 1; } } .lazy-image-comp .final-image { animation: 0.5s animateShow forwards; } .lazy-image-comp .preview-image { background-color: #eee; width: 100%; }
lazy-load属性只针对page与scroll-view下的image组件有效。
每个页面最外层不都是page对象吗,每个image不都在page下面么,
在这里指的是直接位于page容器下面,或者位于scroll-view容器下面。
图片懒加载,在即将进入一定范围(上下三屏)时才开始加载
这也就意味着当你的图片不足三屏的时候,所有的image节点都在范围之内,
这个时候很有可能它们都一次性加载完成了
image控件拉取图片的本质,是使用wx.downloadFile这个接口加载图片的资源, 当加载以后,把加载的图像再绘制出来,这是它本身的一个实现机制。 很多时候是由于图片的格式不规范,例如线上的SSL证书有问题, 或者文件描述信息例如content-type、length等信息不标准不完整, 还有可能是服务器发生了302跳转,等等这些原因,导致图片拉取不成功 看到的现象就是图片没有显示出来 有时候网络不好,加载超时了,图片也不会显示。 并不是因为这个图片它不可以访问, 同样的图片我们用谷歌浏览器或者其他浏览器去加载,可能就是显示的。 对于网络不好这种情况,我们可以使用image组件的binderror这个事件属性去处理, 监听err事件,当监听到错误以后,我们重新给src属性赋值,一般通过这种方法可以解决, 网络不好加载不出来不显示的问题。 302错误,我们浏览网页的时候,浏览器其实是不断的向服务器发出请求,并且不断的接到服务器的应答, 从而决定下一步去做什么事情,这个应答其实就是状态码,在HTTP协议里面状态码是三位数字, 这个状态码分为5类,分别以1、2、3、4、5这五个数字开头的三个数字, 其中302它是服务器返回的一个HTTP状态码,前端想加载A页面,因为网站改版现在不存在了,取而代之的是B页面, 这个时候服务器就可以返回一个302状态码,同时再返回一个B页面的地址,浏览器看到这个状态码和这个地址的时候, 它就会自动的去跳到新的B页面上去。这个就是服务器的302的页面跳转。 涉及到页面跳转的HTTP状态码一共是两个,除了302还有301,这两者都是页面重定向的, 不同的地方在于, 301它是页面永久的转移到了新地址, 302它是请求的网页临时转移到了新网址 我们在微博上经常会看到短链接,其实这种短链接,它就是利用了服务器端的302跳转去实现的, 虽然短链接它有跳转,但是经过测试我们发现目前小程序的image组件也是支持短链接的, 如果给src属性设置一个短链接的图片网址的话,它也是可以加载并且显示的。 微信团队肯定是在内部做了处理的。
mode属性
原图
<view class="page-section">
<text class="page-section__title">三种mode</text>
<text class="page-section__title">scaleToFill</text>
<image style="width:300px;height:300px;" mode="scaleToFill"
src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg"></image>
<text class="page-section__title">aspectFit</text>
<image style="width:300px;height:300px;background-color:#b2b2b2;" mode="aspectFit"
src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg"></image>
<text class="page-section__title">aspectFill</text>
<image style="width:300px;height:300px;" mode="aspectFill"
src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg"></image>
</view>
scaleToFill 变形
缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
aspectFit 上下有灰边
缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。
aspectFill 被截取
缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
背景图片铺满屏幕
最好不用 mode 实现,由wxss样式来实现
<!-- 背景图 -->
<view class="container">
</view>
/* 背景图样式 */ .container { /* 1.新建一个750*1334这样一个大小的背景图片, 并且把分辨率设置为72, */ position: fixed; width: 100%; height: 100%; background-color:azure; top: 0; bottom: 0; left: 0; right: 0; z-index: -1; /* z-index: -1; 在所有组件下面*/ } /* 伪元素 */ .container::after { content: ""; background: url(https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg) no-repeat center center; background-size: cover; /*、 background-size: cover; 和 aspectFit 效果类似 , 保持纵横比例缩放图片, 并且在短边上保持图片是完整的 在另一边是不完整的,有图片是有裁剪的 */ opacity: 0.5; top: 0; bottom: 0; left: 0; right: 0; position: absolute; }
https://github.com/1977474741/image-cropper
它可以让这个图片通过拖拽的方式,然后选择范围,可以任意裁剪。
它的实现原理其实也很简单,它通过四角的一个控制点去控制选择的范围,
四角的控制点它是通过view去渲染出来的,
图片加载完成以后,绘制到canvas画布上,
选定裁剪范围以后,在通过wx.canvasToTempFilePath接口,生成一个临时的图片,
临时图片就是我们需要的裁剪结果。
index/index.wxml
<view class="page-section">
<text class="page-section__title">图片裁剪</text>
<image-cropper id="image-cropper" limit_move="{{true}}" disable_rotate="{{true}}" width="{{width}}" height="{{height}}" imgSrc="{{src}}" bindload="cropperload" bindimageload="loadimage" bindtapcut="clickcut"></image-cropper>
</view>
index/index.js
Page({ /** * 页面的初始数据 */ data: { src: '', width: 250, //宽度 height: 250, //高度 }, startCuting() { //获取到image-cropper对象 this.cropper = this.selectComponent("#image-cropper"); //开始裁剪 this.setData({ src: "https://cdn.nlark.com/yuque/0/2020/jpeg/1252071/1590847767698-f511e86d-f183-4f75-a04d-1b99cd9f0bd7.jpeg", }); wx.showLoading({ title: '加载中' }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { this.startCuting() }, cropperload(e) { console.log("cropper初始化完成"); }, loadimage(e) { console.log("图片加载完成", e.detail); wx.hideLoading(); //重置图片角度、缩放、位置 this.cropper.imgReset(); }, clickcut(e) { console.log(e.detail); console.log(e.detail.url) //点击裁剪框阅览图片 wx.previewImage({ current: e.detail.url, // 当前显示图片的http链接 urls: [e.detail.url] // 需要预览的图片http链接列表 }) }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function () { }, /** * 生命周期函数--监听页面显示 */ onShow: function () { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function () { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function () { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { }, /** * 用户点击右上角分享 */ onShareAppMessage: function () { } })
index/index.json
{
"usingComponents": {
"image-cropper": "../../components/image-cropper/index"
}
}
源码
https://github.com/1977474741/image-cropper
/components/image-cropper/index.wxml
<view class='image-cropper' catchtouchmove='_preventTouchMove'> <view class='main' bindtouchend="_cutTouchEnd" bindtouchstart="_cutTouchStart" bindtouchmove="_cutTouchMove" bindtap="_click"> <view class='content'> <view class='content_top bg_gray {{_flag_bright?"":"bg_black"}}' style="height:{{cut_top}}px;transition-property:{{_cut_animation?'':'background'}}"></view> <view class='content_middle' style="height:{{height}}px;"> <view class='content_middle_left bg_gray {{_flag_bright?"":"bg_black"}}' style="width:{{cut_left}}px;transition-property:{{_cut_animation?'':'background'}}"></view> <view class='content_middle_middle' style="width:{{width}}px;height:{{height}}px;transition-duration: .3s;transition-property:{{_cut_animation?'':'background'}};"> <view class="border border-top-left"></view> <view class="border border-top-right"></view> <view class="border border-right-top"></view> <view class="border border-right-bottom"></view> <view class="border border-bottom-right"></view> <view class="border border-bottom-left"></view> <view class="border border-left-bottom"></view> <view class="border border-left-top"></view> </view> <view class='content_middle_right bg_gray {{_flag_bright?"":"bg_black"}}' style="transition-property:{{_cut_animation?'':'background'}}"></view> </view> <view class='content_bottom bg_gray {{_flag_bright?"":"bg_black"}}' style="transition-property:{{_cut_animation?'':'background'}}"></view> </view> <image bindload="imageLoad" bindtouchstart="_start" bindtouchmove="_move" bindtouchend="_end" style="width:{{img_width ? img_width + 'px' : 'auto'}};height:{{img_height ? img_height + 'px' : 'auto'}};transform:translate3d({{_img_left-img_width/2}}px,{{_img_top-img_height/2}}px,0) scale({{scale}}) rotate({{angle}}deg);transition-duration:{{_cut_animation?.4:0}}s;" class='img' src='{{imgSrc}}'></image> </view> <canvas canvas-id='image-cropper' disable-scroll="true" style="width:{{_canvas_width * export_scale}}px;height:{{_canvas_height * export_scale}}px;left:{{canvas_left}}px;top:{{canvas_top}}px" class='image-cropper-canvas'></canvas> </view>
/components/image-cropper/index.js
Component({ properties: { /** * 图片路径 */ 'imgSrc': { type: String }, /** * 裁剪框高度 */ 'height': { type: Number, value: 200 }, /** * 裁剪框宽度 */ 'width': { type: Number, value: 200 }, /** * 裁剪框最小尺寸 */ 'min_width': { type: Number, value: 100 }, 'min_height': { type: Number, value: 100 }, /** * 裁剪框最大尺寸 */ 'max_width': { type: Number, value: 300 }, 'max_height': { type: Number, value: 300 }, /** * 裁剪框禁止拖动 */ 'disable_width': { type: Boolean, value: false }, 'disable_height': { type: Boolean, value: false }, /** * 锁定裁剪框比例 */ 'disable_ratio':{ type: Boolean, value: false }, /** * 生成的图片尺寸相对剪裁框的比例 */ 'export_scale': { type: Number, value: 3 }, /** * 生成的图片质量0-1 */ 'quality': { type: Number, value: 1 }, 'cut_top': { type: Number, value: null }, 'cut_left': { type: Number, value: null }, /** * canvas上边距(不设置默认不显示) */ 'canvas_top': { type: Number, value: null }, /** * canvas左边距(不设置默认不显示) */ 'canvas_left': { type: Number, value: null }, /** * 图片宽度 */ 'img_width': { type: null, value: null }, /** * 图片高度 */ 'img_height': { type: null, value: null }, /** * 图片缩放比 */ 'scale': { type: Number, value: 1 }, /** * 图片旋转角度 */ 'angle': { type: Number, value: 0 }, /** * 最小缩放比 */ 'min_scale': { type: Number, value: 0.5 }, /** * 最大缩放比 */ 'max_scale': { type: Number, value: 2 }, /** * 是否禁用旋转 */ 'disable_rotate': { type: Boolean, value: false }, /** * 是否限制移动范围(剪裁框只能在图片内) */ 'limit_move':{ type: Boolean, value: false } }, data: { el: 'image-cropper', //暂时无用 info: wx.getSystemInfoSync(), MOVE_THROTTLE:null,//触摸移动节流settimeout MOVE_THROTTLE_FLAG: true,//节流标识 INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) TIME_BG: null,//背景变暗延时函数 TIME_CUT_CENTER:null, _touch_img_relative: [{ x: 0, y: 0 }], //鼠标和图片中心的相对位置 _flag_cut_touch:false,//是否是拖动裁剪框 _hypotenuse_length: 0, //双指触摸时斜边长度 _flag_img_endtouch: false, //是否结束触摸 _flag_bright: true, //背景是否亮 _canvas_overflow:true,//canvas缩略图是否在屏幕外面 _canvas_width:200, _canvas_height:200, origin_x: 0.5, //图片旋转中心 origin_y: 0.5, //图片旋转中心 _cut_animation: false,//是否开启图片和裁剪框过渡 _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距 _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距 watch: { //监听截取框宽高变化 width(value, that) { if (value < that.data.min_width){ that.setData({ width: that.data.min_width }); } that._computeCutSize(); }, height(value, that) { if (value < that.data.min_height) { that.setData({ height: that.data.min_height }); } that._computeCutSize(); }, angle(value, that){ //停止居中裁剪框,继续修改图片位置 that._moveStop(); if(that.data.limit_move){ if (that.data.angle % 90) { that.setData({ angle: Math.round(that.data.angle / 90) * 90 }); return; } } }, _cut_animation(value, that){ //开启过渡300毫秒之后自动关闭 clearTimeout(that.data._cut_animation_time); if (value){ that.data._cut_animation_time = setTimeout(()=>{ that.setData({ _cut_animation:false }); },300) } }, limit_move(value, that){ if (value) { if (that.data.angle%90){ that.setData({ angle: Math.round(that.data.angle / 90)*90 }); } that._imgMarginDetectionScale(); !that.data._canvas_overflow && that._draw(); } }, canvas_top(value, that){ that._canvasDetectionPosition(); }, canvas_left(value, that){ that._canvasDetectionPosition(); }, imgSrc(value, that){ that.pushImg(); }, cut_top(value, that) { that._cutDetectionPosition(); if (that.data.limit_move) { !that.data._canvas_overflow && that._draw(); } }, cut_left(value, that) { that._cutDetectionPosition(); if (that.data.limit_move) { !that.data._canvas_overflow && that._draw(); } } } }, attached() { this.data.info = wx.getSystemInfoSync(); //启用数据监听 this._watcher(); this.data.INIT_IMGWIDTH = this.data.img_width; this.data.INIT_IMGHEIGHT = this.data.img_height; this.setData({ _canvas_height: this.data.height, _canvas_width: this.data.width, }); this._initCanvas(); this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc); //根据开发者设置的图片目标尺寸计算实际尺寸 this._initImageSize(); //设置裁剪框大小>设置图片尺寸>绘制canvas this._computeCutSize(); //检查裁剪框是否在范围内 this._cutDetectionPosition(); //检查canvas是否在范围内 this._canvasDetectionPosition(); //初始化完成 this.triggerEvent('load', { cropper: this }); }, methods: { /** * 上传图片 */ upload() { let that = this; wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success(res) { const tempFilePaths = res.tempFilePaths[0]; that.pushImg(tempFilePaths); wx.showLoading({ title: '加载中...' }) } }) }, /** * 返回图片信息 拉取图片信息 */ getImg(getCallback) { this._draw(()=>{ wx.canvasToTempFilePath({ width: this.data.width * this.data.export_scale, height: Math.round(this.data.height * this.data.export_scale), destWidth: this.data.width * this.data.export_scale, destHeight: Math.round(this.data.height) * this.data.export_scale, fileType: 'png', quality: this.data.quality, canvasId: this.data.el, success: (res) => { getCallback({ url: res.tempFilePath, width: this.data.width * this.data.export_scale, height: this.data.height * this.data.export_scale }); } }, this) }); }, /** * 设置图片动画 * { * x:10,//图片在原有基础上向下移动10px * y:10,//图片在原有基础上向右移动10px * angle:10,//图片在原有基础上旋转10deg * scale:0.5,//图片在原有基础上增加0.5倍 * } */ setTransform(transform) { if (!transform) return; if (!this.data.disable_rotate){ this.setData({ angle: transform.angle ? this.data.angle + transform.angle : this.data.angle }); } var scale = this.data.scale; if (transform.scale) { scale = this.data.scale + transform.scale; scale = scale <= this.data.min_scale ? this.data.min_scale : scale; scale = scale >= this.data.max_scale ? this.data.max_scale : scale; } this.data.scale = scale; let cutX = this.data.cut_left; let cutY = this.data.cut_top; if (transform.cutX){ this.setData({ cut_left: cutX + transform.cutX }); this.data.watch.cut_left(null, this); } if (transform.cutY){ this.setData({ cut_top: cutY + transform.cutY }); this.data.watch.cut_top(null, this); } this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top; this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left; //图像边缘检测,防止截取到空白 this._imgMarginDetectionScale(); //停止居中裁剪框,继续修改图片位置 this._moveDuring(); this.setData({ scale: this.data.scale, _img_top: this.data._img_top, _img_left: this.data._img_left }); !this.data._canvas_overflow && this._draw(); //可以居中裁剪框了 this._moveStop();//结束操作 }, /** * 设置剪裁框位置 */ setCutXY(x,y){ this.setData({ cut_top: y, cut_left:x }); }, /** * 设置剪裁框尺寸 */ setCutSize(w,h){ this.setData({ width: w, height:h }); this._computeCutSize(); }, /** * 设置剪裁框和图片居中 */ setCutCenter() { let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; //顺序不能变 this.setData({ _img_top: this.data._img_top - this.data.cut_top + cut_top, cut_top: cut_top, //截取的框上边距 _img_left: this.data._img_left - this.data.cut_left + cut_left, cut_left: cut_left, //截取的框左边距 }); }, _setCutCenter(){ let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; this.setData({ cut_top: cut_top, //截取的框上边距 cut_left: cut_left, //截取的框左边距 }); }, /** * 设置剪裁框宽度-即将废弃 */ setWidth(width) { this.setData({ width: width }); this._computeCutSize(); }, /** * 设置剪裁框高度-即将废弃 */ setHeight(height) { this.setData({ height: height }); this._computeCutSize(); }, /** * 是否锁定旋转 */ setDisableRotate(value){ this.data.disable_rotate = value; }, /** * 是否限制移动 */ setLimitMove(value){ this.setData({ _cut_animation: true, limit_move: !!value }); }, /** * 初始化图片,包括位置、大小、旋转角度 */ imgReset() { this.setData({ scale: 1, angle: 0, _img_top: wx.getSystemInfoSync().windowHeight / 2, _img_left: wx.getSystemInfoSync().windowWidth / 2, }) }, /** * 加载(更换)图片 */ pushImg(src) { if (src) { this.setData({ imgSrc: src }); //发现是手动赋值直接返回,交给watch处理 return; } // getImageInfo接口传入 src: '' 会导致内存泄漏 if (!this.data.imgSrc) return; wx.getImageInfo({ src: this.data.imgSrc, success: (res) => { this.data.imageObject = res; //图片非本地路径需要换成本地路径 if (this.data.imgSrc.search(/tmp/) == -1){ this.setData({ imgSrc: res.path }); } //计算最后图片尺寸 this._imgComputeSize(); if (this.data.limit_move) { //限制移动,不留空白处理 this._imgMarginDetectionScale(); } this._draw(); }, fail: (err) => { this.setData({ imgSrc: '' }); } }); }, imageLoad(e){ setTimeout(()=>{ this.triggerEvent('imageload', this.data.imageObject); },1000) }, /** * 设置图片放大缩小 */ setScale(scale) { if (!scale) return; this.setData({ scale: scale }); !this.data._canvas_overflow && this._draw(); }, /** * 设置图片旋转角度 */ setAngle(angle) { if (!angle) return; this.setData({ _cut_animation: true, angle: angle }); this._imgMarginDetectionScale(); !this.data._canvas_overflow && this._draw(); }, _initCanvas() { //初始化canvas if (!this.data.ctx){ this.data.ctx = wx.createCanvasContext("image-cropper", this); } }, /** * 根据开发者设置的图片目标尺寸计算实际尺寸 */ _initImageSize(){ //处理宽高特殊单位 %>px if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) { let width = this.data.INIT_IMGWIDTH.replace("%", ""); this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width; } if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) { let height = this.data.img_height.replace("%", ""); this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height; } }, /** * 检测剪裁框位置是否在允许的范围内(屏幕内) */ _cutDetectionPosition(){ let _cutDetectionPositionTop = () => { //检测上边距是否在范围内 if (this.data.cut_top < 0) { this.setData({ cut_top: 0 }); } if (this.data.cut_top > this.data.info.windowHeight - this.data.height) { this.setData({ cut_top: this.data.info.windowHeight - this.data.height }); } }, _cutDetectionPositionLeft = () => { //检测左边距是否在范围内 if (this.data.cut_left < 0) { this.setData({ cut_left: 0 }); } if (this.data.cut_left > this.data.info.windowWidth - this.data.width) { this.setData({ cut_left: this.data.info.windowWidth - this.data.width }); } }; //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中) if (this.data.cut_top == null && this.data.cut_left == null) { this._setCutCenter(); } else if (this.data.cut_top != null && this.data.cut_left != null){ _cutDetectionPositionTop(); _cutDetectionPositionLeft(); } else if (this.data.cut_top != null && this.data.cut_left == null) { _cutDetectionPositionTop(); this.setData({ cut_left: (this.data.info.windowWidth - this.data.width) / 2 }); } else if (this.data.cut_top == null && this.data.cut_left != null) { _cutDetectionPositionLeft(); this.setData({ cut_top: (this.data.info.windowHeight - this.data.height) / 2 }); } }, /** * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染 * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外 */ _canvasDetectionPosition(){ if(this.data.canvas_top == null && this.data.canvas_left == null) { this.data._canvas_overflow = false; this.setData({ canvas_top: -5000, canvas_left: -5000 }); }else if(this.data.canvas_top != null && this.data.canvas_left != null) { if (this.data.canvas_top < - this.data.height || this.data.canvas_top > this.data.info.windowHeight) { this.data._canvas_overflow = true; } else { this.data._canvas_overflow = false; } }else if(this.data.canvas_top != null && this.data.canvas_left == null) { this.setData({ canvas_left: 0 }); } else if (this.data.canvas_top == null && this.data.canvas_left != null) { this.setData({ canvas_top: 0 }); if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) { this.data._canvas_overflow = true; } else { this.data._canvas_overflow = false; } } }, /** * 图片边缘检测-位置 */ _imgMarginDetectionPosition(scale) { if (!this.data.limit_move) return; let left = this.data._img_left; let top = this.data._img_top; var scale = scale || this.data.scale; let img_width = this.data.img_width; let img_height = this.data.img_height; if (this.data.angle / 90 % 2) { img_width = this.data.img_height; img_height = this.data.img_width; } left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2; left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2; top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2; top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2; this.setData({ _img_left: left, _img_top: top, scale: scale }) }, /** * 图片边缘检测-缩放 */ _imgMarginDetectionScale(){ if (!this.data.limit_move)return; let scale = this.data.scale; let img_width = this.data.img_width; let img_height = this.data.img_height; if (this.data.angle / 90 % 2) { img_width = this.data.img_height; img_height = this.data.img_width; } if (img_width * scale < this.data.width){ scale = this.data.width / img_width; } if (img_height * scale < this.data.height) { scale = Math.max(scale,this.data.height / img_height); } this._imgMarginDetectionPosition(scale); }, _setData(obj) { let data = {}; for (var key in obj) { if (this.data[key] != obj[key]){ data[key] = obj[key]; } } this.setData(data); return data; }, /** * 计算图片尺寸 */ _imgComputeSize() { let img_width = this.data.img_width, img_height = this.data.img_height; if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { //默认按图片最小边 = 对应裁剪框尺寸 img_width = this.data.imageObject.width; img_height = this.data.imageObject.height; if (img_width / img_height > this.data.width / this.data.height){ img_height = this.data.height; img_width = this.data.imageObject.width / this.data.imageObject.height * img_height; }else{ img_width = this.data.width; img_height = this.data.imageObject.height / this.data.imageObject.width * img_width; } } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT; } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) { img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH; } this.setData({ img_width: img_width, img_height: img_height }); }, //改变截取框大小 _computeCutSize() { if (this.data.width > this.data.info.windowWidth) { this.setData({ width: this.data.info.windowWidth, }); } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth){ this.setData({ cut_left: this.data.info.windowWidth - this.data.cut_left, }); }; if (this.data.height > this.data.info.windowHeight) { this.setData({ height: this.data.info.windowHeight, }); } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight){ this.setData({ cut_top: this.data.info.windowHeight - this.data.cut_top, }); } !this.data._canvas_overflow && this._draw(); }, //开始触摸 _start(event) { this.data._flag_img_endtouch = false; if (event.touches.length == 1) { //单指拖动 this.data._touch_img_relative[0] = { x: (event.touches[0].clientX - this.data._img_left), y: (event.touches[0].clientY - this.data._img_top) } } else { //双指放大 let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX); let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY); this.data._touch_img_relative = [{ x: (event.touches[0].clientX - this.data._img_left), y: (event.touches[0].clientY - this.data._img_top) }, { x: (event.touches[1].clientX - this.data._img_left), y: (event.touches[1].clientY - this.data._img_top) }]; this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); } !this.data._canvas_overflow && this._draw(); }, _move_throttle(){ //安卓需要节流 if (this.data.info.platform =='android'){ clearTimeout(this.data.MOVE_THROTTLE); this.data.MOVE_THROTTLE = setTimeout(() => { this.data.MOVE_THROTTLE_FLAG = true; }, 1000 / 40) return this.data.MOVE_THROTTLE_FLAG; }else{ this.data.MOVE_THROTTLE_FLAG = true; } }, _move(event) { if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return; this.data.MOVE_THROTTLE_FLAG = false; this._move_throttle(); this._moveDuring(); if (event.touches.length == 1) { //单指拖动 let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x), top = (event.touches[0].clientY - this.data._touch_img_relative[0].y); //图像边缘检测,防止截取到空白 this.data._img_left = left; this.data._img_top = top; this._imgMarginDetectionPosition(); this.setData({ _img_left: this.data._img_left, _img_top: this.data._img_top }); } else { //双指放大 let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)), height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)), hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)), scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length), current_deg = 0; scale = scale <= this.data.min_scale ? this.data.min_scale : scale; scale = scale >= this.data.max_scale ? this.data.max_scale : scale; //图像边缘检测,防止截取到空白 this.data.scale = scale; this._imgMarginDetectionScale(); //双指旋转(如果没禁用旋转) let _touch_img_relative = [{ x: (event.touches[0].clientX - this.data._img_left), y: (event.touches[0].clientY - this.data._img_top) }, { x: (event.touches[1].clientX - this.data._img_left), y: (event.touches[1].clientY - this.data._img_top) }]; if (!this.data.disable_rotate){ let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x); let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x); let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x); let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x); //当前旋转的角度 let first_deg = first_atan - first_atan_old, second_deg = second_atan - second_atan_old; if (first_deg != 0) { current_deg = first_deg; } else if (second_deg != 0) { current_deg = second_deg; } } this.data._touch_img_relative = _touch_img_relative; this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); //更新视图 this.setData({ angle: this.data.angle + current_deg, scale: this.data.scale }); } !this.data._canvas_overflow && this._draw(); }, //结束操作 _end(event) { this.data._flag_img_endtouch = true; this._moveStop(); }, //点击中间剪裁框处理 _click(event) { if (!this.data.imgSrc) { //调起上传 this.upload(); return; } this._draw(()=>{ let x = event.detail ? event.detail.x : event.touches[0].clientX; let y = event.detail ? event.detail.y : event.touches[0].clientY; if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) { //生成图片并回调 // wx.canvasToTempFilePath 生成临时图片 wx.canvasToTempFilePath({ width: this.data.width * this.data.export_scale, height: Math.round(this.data.height * this.data.export_scale), destWidth: this.data.width * this.data.export_scale, destHeight: Math.round(this.data.height) * this.data.export_scale, fileType: 'png', quality: this.data.quality, canvasId: this.data.el, success: (res) => { // 派发 tapcut 事件 this.triggerEvent('tapcut', { url: res.tempFilePath, //临时url地址 width: this.data.width * this.data.export_scale, height: this.data.height * this.data.export_scale }); } }, this) } }); }, //渲染 _draw(callback) { if (!this.data.imgSrc) return; let draw = () => { //图片实际大小 let img_width = this.data.img_width * this.data.scale * this.data.export_scale; let img_height = this.data.img_height * this.data.scale * this.data.export_scale; //canvas和图片的相对距离 var xpos = this.data._img_left - this.data.cut_left; var ypos = this.data._img_top - this.data.cut_top; //旋转画布 this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale); this.data.ctx.rotate(this.data.angle * Math.PI / 180); this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height); this.data.ctx.draw(false, () => { callback && callback(); }); } if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height){ //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方 this.setData({ _canvas_height: this.data.height, _canvas_width: this.data.width, },()=>{ //延迟40毫秒防止点击过快出现拉伸或裁剪过多 setTimeout(() => { draw(); }, 40); }); }else{ draw(); } }, //裁剪框处理 _cutTouchMove(e) { if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) { if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return; //节流 this.data.MOVE_THROTTLE_FLAG = false; this._move_throttle(); let width = this.data.width, height = this.data.height, cut_top = this.data.cut_top, cut_left = this.data.cut_left, size_correct = () => { width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width; height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height; }, size_inspect = () => { if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) { size_correct(); return false; } else { size_correct(); return true; } }; height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY)); switch (this.data.CUT_START.corner) { case 1: width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; if (this.data.disable_ratio) { height = width / (this.data.width / this.data.height) } if (!size_inspect()) return; cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width); break case 2: width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; if (this.data.disable_ratio) { height = width / (this.data.width / this.data.height) } if (!size_inspect()) return; cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height) cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width) break case 3: width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; if (this.data.disable_ratio) { height = width / (this.data.width / this.data.height) } if (!size_inspect()) return; cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height); break case 4: width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; if (this.data.disable_ratio) { height = width / (this.data.width / this.data.height) } if (!size_inspect()) return; break } if (!this.data.disable_width && !this.data.disable_height) { this.setData({ width: width, cut_left: cut_left, height: height, cut_top: cut_top, }) } else if (!this.data.disable_width) { this.setData({ width: width, cut_left: cut_left }) } else if (!this.data.disable_height) { this.setData({ height: height, cut_top: cut_top }) } this._imgMarginDetectionScale(); } }, _cutTouchStart(e) { let currentX = e.touches[0].clientX; let currentY = e.touches[0].clientY; let cutbox_top4 = this.data.cut_top + this.data.height - 30; let cutbox_bottom4 = this.data.cut_top + this.data.height + 20; let cutbox_left4 = this.data.cut_left + this.data.width - 30; let cutbox_right4 = this.data.cut_left + this.data.width + 30; let cutbox_top3 = this.data.cut_top - 30; let cutbox_bottom3 = this.data.cut_top + 30; let cutbox_left3 = this.data.cut_left + this.data.width - 30; let cutbox_right3 = this.data.cut_left + this.data.width + 30; let cutbox_top2 = this.data.cut_top - 30; let cutbox_bottom2 = this.data.cut_top + 30; let cutbox_left2 = this.data.cut_left - 30; let cutbox_right2 = this.data.cut_left + 30; let cutbox_top1 = this.data.cut_top + this.data.height - 30; let cutbox_bottom1 = this.data.cut_top + this.data.height + 30; let cutbox_left1 = this.data.cut_left - 30; let cutbox_right1 = this.data.cut_left + 30; if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) { this._moveDuring(); this.data._flag_cut_touch = true; this.data._flag_img_endtouch = true; this.data.CUT_START = { width: this.data.width, height: this.data.height, x: currentX, y: currentY, corner: 4 } } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) { this._moveDuring(); this.data._flag_cut_touch = true; this.data._flag_img_endtouch = true; this.data.CUT_START = { width: this.data.width, height: this.data.height, x: currentX, y: currentY, cut_top: this.data.cut_top, cut_left: this.data.cut_left, corner: 3 } } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) { this._moveDuring(); this.data._flag_cut_touch = true; this.data._flag_img_endtouch = true; this.data.CUT_START = { width: this.data.width, height: this.data.height, cut_top: this.data.cut_top, cut_left: this.data.cut_left, x: currentX, y: currentY, corner: 2 } } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) { this._moveDuring(); this.data._flag_cut_touch = true; this.data._flag_img_endtouch = true; this.data.CUT_START = { width: this.data.width, height: this.data.height, cut_top: this.data.cut_top, cut_left: this.data.cut_left, x: currentX, y: currentY, corner: 1 } } }, _cutTouchEnd(e) { this._moveStop(); this.data._flag_cut_touch = false; }, //停止移动时需要做的操作 _moveStop() { //清空之前的自动居中延迟函数并添加最新的 clearTimeout(this.data.TIME_CUT_CENTER); this.data.TIME_CUT_CENTER = setTimeout(() => { //动画启动 if (!this.data._cut_animation) { this.setData({ _cut_animation: true }); } this.setCutCenter(); }, 1000) //清空之前的背景变化延迟函数并添加最新的 clearTimeout(this.data.TIME_BG); this.data.TIME_BG = setTimeout(() => { if (this.data._flag_bright) { this.setData({ _flag_bright: false }); } }, 2000) }, //移动中 _moveDuring() { //清空之前的自动居中延迟函数 clearTimeout(this.data.TIME_CUT_CENTER); //清空之前的背景变化延迟函数 clearTimeout(this.data.TIME_BG); //高亮背景 if (!this.data._flag_bright) { this.setData({ _flag_bright: true }); } }, //监听器 _watcher() { Object.keys(this.data).forEach(v => { this._observe(this.data, v, this.data.watch[v]); }) }, _observe(obj, key, watchFun) { var val = obj[key]; Object.defineProperty(obj, key, { configurable: true, enumerable: true, set:(value) => { val = value; watchFun && watchFun(val, this); }, get() { if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key)!=-1){ let ret = parseFloat(parseFloat(val).toFixed(3)); if (typeof val == "string" && val.indexOf("%") != -1){ ret+='%'; } return ret; } return val; } }) }, _preventTouchMove() { } } })
/components/image-cropper/index.json
{
"component": true
}
/components/image-cropper/index.wxss
.image-cropper{ background:rgba(14, 13, 13,.8); position: fixed; top:0; left:0; width:100vw; height:100vh; z-index: 1; } .main{ position: absolute; width:100vw; height:100vh; overflow: hidden; } .content{ z-index: 9; position: absolute; width:100vw; height:100vh; display: flex; flex-direction:column; pointer-events:none; } .bg_black{ background: rgba(0, 0, 0, 0.8)!important; } .bg_gray{ background: rgba(0, 0, 0, 0.45); transition-duration: .35s; } .content>.content_top{ pointer-events:none; } .content>.content_middle{ display: flex; height: 200px; width:100%; } .content_middle_middle{ width:200px; box-sizing:border-box; position: relative; transition-duration: .3s; } .content_middle_right{ flex: auto; } .content>.content_bottom{ flex: auto; } .image-cropper .img{ z-index: 2; top:0; left:0; position: absolute; border:none; width:100%; backface-visibility: hidden; transform-origin:center; } .image-cropper-canvas{ position: fixed; background: white; width:150px; height:150px; z-index: 10; top:-200%; pointer-events:none; } .border{ background: white; pointer-events:auto; position:absolute; } .border-top-left{ left:-2.5px; top:-2.5px; height:2.5px; width:33rpx; } .border-top-right{ right:-2.5px; top:-2.5px; height:2.5px; width:33rpx; } .border-right-top{ top:-1px; width:2.5px; height:30rpx; right:-2.5px; } .border-right-bottom{ width:2.5px; height:30rpx; right:-2.5px; bottom:-1px; } .border-bottom-left{ height:2.5px; width:33rpx; bottom:-2.5px; left:-2.5px; } .border-bottom-right{ height:2.5px; width:33rpx; bottom:-2.5px; right:-2.5px; } .border-left-top{ top:-1px; width:2.5px; height:30rpx; left:-2.5px; } .border-left-bottom{ width:2.5px; height:30rpx; left:-2.5px; bottom:-1px; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。