一、与image组件有关的技术问题

1.1 什么是WebP?

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格式的图片,直接可以用谷歌浏览器打开

1.2 如何理解show-menu-by-longpress属性?

show-menu-by-longpress是image组件的一个boolean属性,开启它的时候就是代表开启了长按图片,
显示识别小程序码的一个菜单
但是它这种识别是在wx.previewImage接口调用的时候,开始预览图片的时候,才可以识别

实现的效果

1.3 官方的image组件已经有一个lazy-load属性,为什么我们还要再另外自定义实现一个懒加载组件呢?

image组件的lazy-load属性,它规定图片即将进入一定范围之内的时候,
这个范围是上、下三屏,才开始去加载图片。
上下三屏其实是一个很大的范围空间,一般情况下会导致我们的图片,图片在页面加载的时候,第一次就加载了几十张图片。

实际体验

1.3.1 官方mage组件的lazy-load属性

刚进入页面不进行任何操作 – 33个请求

开启lazy-load属性的image组件,在我们使用以后,第一页,什么也不操作,图片请求了33次,实际加载32次。
并且image组件提供的图片懒加载功能,它只支持针对 page 与 scroll-view 下面的image组件有效

往上滚动之后,有新的图片加载进来

官方mage组件的lazy-load属性,是有效果的,是分上下三屏,在我们往上滚动的时候,有新的图片加载进来
1.3.2 mina-lazy-image 图片懒加载自定义组件
https://developers.weixin.qq.com/community/develop/article/doc/0002e4522b0ef8f1b1b992b0151813

刚进入页面不进行任何操作 – 11个请求

不是官方image组件一下子加载很多的情况

往上滚动,会有新的加载

1.4 mina-lazy-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%;
}

二、开发中经常遇到的问题

2.1 小程序官方组件 image 的 lazy-load 为什么有些时候不起作用?

lazy-load属性只针对page与scroll-view下的image组件有效。每个页面最外层不都是page对象吗,每个image不都在page下面么,
在这里指的是直接位于page容器下面,或者位于scroll-view容器下面。图片懒加载,在即将进入一定范围(上下三屏)时才开始加载
这也就意味着当你的图片不足三屏的时候,所有的image节点都在范围之内,
这个时候很有可能它们都一次性加载完成了

2.2 为什么有时候图片链接可正常访问,但 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属性设置一个短链接的图片网址的话,它也是可以加载并且显示的。
微信团队肯定是在内部做了处理的。

2.3 小程序的背景图片如何实现全屏,如何适配所有机型?

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;
}

2.4 如何剪切图片?

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,//触摸移动节流settimeoutMOVE_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();//设置裁剪框大小>设置图片尺寸>绘制canvasthis._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() {//初始化canvasif (!this.data.ctx){this.data.ctx = wx.createCanvasContext("image-cropper", this);}},/*** 根据开发者设置的图片目标尺寸计算实际尺寸*/_initImageSize(){//处理宽高特殊单位 %>pxif (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);breakcase 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)breakcase 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);breakcase 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;
}

微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.10 image组件,如何实现图片懒加载?相关推荐

  1. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.6 scroll-view组件,在小程序中如何实现滚动锚定,如何渲染一个滚动的长列表?

    scroll-view 是一个可以滚动的视图区域的容器组件. 一.重要属性 scroll-view 的滚动属性,实现了两套功能 左右或上下滚动 下拉更新 1.1 与滚动有关的属性: scroll-x ...

  2. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.5 可移动容器及可移动区域,以及如何实现侧滑删除功能

    一.学习使用moveable-view与movable-area组件 1.1 关于元素的定位 static 静态定位 元素在页面流动的当前位置定位,这个时候它的top.left.right.botto ...

  3. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.9 页面链接组件,如何自定义一个导航栏?

    一.小程序中的导航组件 functional-page-navigator 仅在插件中有效,用于跳转到插件功能页. navigator 小程序标准的导航组件 小程序插件是对一些js接口.自定义组件或页 ...

  4. 微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.3 rich-text 组件,以及如何单击预览它的节点图片并保存

    一.rich-text组件 nodes: [{name: 'div',attrs: {class: 'div_class',style: 'line-height: 20px;padding:20px ...

  5. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.7 网络接口简介(七)学习EventChannel对象

    零.回顾 在之前我们自定义实现picker-view组件的时候, 曾经使用过一个pop-up的自定义组件, 这个组件可以在底部滑入一个面板, 现在我们把登录按钮放在底部滑入的面板之上, 然后在完成登录 ...

  6. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.5 网络接口简介(五)基于Promise+await、async关键字改写登录模块

    零.回顾 在上节课我们主要实践练习了Promise的三个方法,包括any.all.race. 现在我们对Promise变成已经有了一个大致的了解. 这节课我们尝试将登录模块使用Promise编程方式进 ...

  7. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.6 网络接口简介(六)关于Page页面隐藏代码执行及Promise对象catch处理的补充

    零.回顾 在上节课我们主要是基于 Promise 加 await.async关键字改写了登录模块代码, 但是我们在自动登录这一块的代码仍然有问题,这节课我们看一下, 如何在接口调用中实现微信用户的自动 ...

  8. 微信小程序全栈开发实践 第三章 微信小程序开发常用的API介绍及使用 -- 3.9 网络接口简介(九)扩展wxp模块的request3方法,实现用户登录的自动融合

    零.回顾 在上节课我们主要介绍了观察者模式, 并据此模式实现了一个event模块, 这节课我们基础用户登录的自动整合, 尝试在wxp模块当中扩展出一个request3这样的一个接口. 一.在wxp组件 ...

  9. spring boot @value_spring+vue全栈开发实战-第二章Spring Boot 基础配置-笔记0302-2020

    Spring Boot 基础配置 1. Web 容器配置 2.Properties 配置 3.类型安全配置属性 1. Web 容器配置 a.常规配置 在 Spring Boot 项 目 中,可以内置 ...

最新文章

  1. 北京交通大学计算机科学与技术研究生导师,熊轲_北京交通大学研究生导师信息...
  2. “勒索病毒”为什么盯上了比特币?
  3. 大数据之父_大新闻!Python 之父重新出山,加入微软开发部
  4. 我有一个竞争方法,让用户去选择你的产品而不是别人
  5. C/C++字节对齐总结
  6. python等值面图平滑_离散点插值方法、等值线的绘制及平滑技巧
  7. 推挽输出和开漏输出_关于51单片机IO口的输出模式结构
  8. python多大学_用Python看看你的大学A4纸还剩多少!
  9. 【华为云技术分享】玩转云上数据湖,解析Serverless 技术落地
  10. NGINX访问日志和错误日志
  11. SEP客户端部署方式及故障处理手册____借鉴文档的思路方法
  12. oracle分析函数大全非常详细
  13. c语言禁忌搜索算法源代码,禁忌搜索算法CC++源代码.pdf
  14. Linux系统:page fault
  15. 第二十七课 awk工具
  16. 成都拓嘉启远:拼多多直通车推广怎么做才能有开好
  17. Sketch Mirror不能用?那就用Skala Preview手机预览Sketch
  18. 湘潭大学计算机科学与技术录取分数线,计算机科学与技术专业分数线各大学排名(湖南)...
  19. 【游戏开发实战】Unity使用ShaderGraph配合粒子系统,制作子弹拖尾特效(Fate/stay night金闪闪的大招效果)
  20. 利用ruby演示程序执行

热门文章

  1. 华滋先生:关于打造个人品牌一些要注意的点
  2. DMGI:Unsupervised Attributed Multiplex Network Embedding
  3. URP自定义屏幕后处理
  4. ListView setAdapter has already been called 异常
  5. 一个典型的信息化建设该如何规划?
  6. RDP 优化操作 微软远程桌面 开启显卡加速、60FPS、USB设备重定向
  7. 科技新品 | 戴尔外星人曲面量子点OLED电竞显示器;TCL首款笔记本电脑及消费级XR眼镜;绘王新一代便携数位屏...
  8. 风电光伏的场景生成与消减-matlab代码 可利用蒙特卡洛模拟或者拉丁超立方生成光伏和风电出力场景
  9. 我学编程时最后悔的事!
  10. 导入SVN版本库,提示svnadmin: E000002: Can't open file