当前位置:   article > 正文

微信小程序全栈开发实践 第二章 微信小程序组件介绍及使用 -- 2.10 image组件,如何实现图片懒加载?_图片懒加载。只针对page与scroll-view下的image有效

图片懒加载。只针对page与scroll-view下的image有效

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

1.1 什么是WebP?
webp是image组件的一个boolean属性,开启这个属性之后,代表url可以设置webp这种格式的图片。

 webP是一种同时提供了有损压缩与无损压缩的,并且是可逆的图片压缩的这种文件格式,这种文件
格式是由谷歌推出的。

image组件模式是不解析 webP这种图片格式的,它只支持网络图片资源,只有开启了webp属性之后,
才可以解析 webP这种图片网址。

那么我们为什么要使用 webP这种图片格式呢?它有什么优势呢?

 webP的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,并且拥有肉眼识别无差异的图像质量,
同时它还提供了无损以及有损这两种图片压缩模式,还提供了alpha透明,以及动画的特性,对JPEGPNG等这些图片格式
的转化都有支持。并且转化的效果都是相当优秀的。

 webP既可以替代JPEGPNG这些静态的图片,它也可以替代GIF这种动态图片。

转化 webP的格式的方法
智图:https://zhitu.isux.us/
在线转化网址  将普通的JPEGGIFPNG的图片,转化成一个 webP的格式
转化后的webP格式的图片,直接可以用谷歌浏览器打开

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
1.2 如何理解show-menu-by-longpress属性?
show-menu-by-longpress是image组件的一个boolean属性,开启它的时候就是代表开启了长按图片,
显示识别小程序码的一个菜单
但是它这种识别是在wx.previewImage接口调用的时候,开始预览图片的时候,才可以识别
  • 1
  • 2
  • 3

实现的效果
在这里插入图片描述

1.3 官方的image组件已经有一个lazy-load属性,为什么我们还要再另外自定义实现一个懒加载组件呢?
image组件的lazy-load属性,它规定图片即将进入一定范围之内的时候,
这个范围是上、下三屏,才开始去加载图片。
上下三屏其实是一个很大的范围空间,一般情况下会导致我们的图片,图片在页面加载的时候,第一次就加载了几十张图片。
  • 1
  • 2
  • 3

在这里插入图片描述

实际体验

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

刚进入页面不进行任何操作 – 33个请求
在这里插入图片描述

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

往上滚动之后,有新的图片加载进来
在这里插入图片描述

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

刚进入页面不进行任何操作 – 11个请求
在这里插入图片描述

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

往上滚动,会有新的加载

在这里插入图片描述

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()
监听完成,要停止监听
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码
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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

/***/ })
/******/ ]);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227

miniprogram_npm/mina-lazy-image/index.json

{
  "component": true,
  "usingComponents": {}
}
  • 1
  • 2
  • 3
  • 4

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

2.1 小程序官方组件 image 的 lazy-load 为什么有些时候不起作用?
lazy-load属性只针对page与scroll-view下的image组件有效。

每个页面最外层不都是page对象吗,每个image不都在page下面么,
在这里指的是直接位于page容器下面,或者位于scroll-view容器下面。

图片懒加载,在即将进入一定范围(上下三屏)时才开始加载
这也就意味着当你的图片不足三屏的时候,所有的image节点都在范围之内,
这个时候很有可能它们都一次性加载完成了
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
2.2 为什么有时候图片链接可正常访问,但 image 组件加载不出来图片?
image控件拉取图片的本质,是使用wx.downloadFile这个接口加载图片的资源,
当加载以后,把加载的图像再绘制出来,这是它本身的一个实现机制。

很多时候是由于图片的格式不规范,例如线上的SSL证书有问题,
或者文件描述信息例如content-type、length等信息不标准不完整,
还有可能是服务器发生了302跳转,等等这些原因,导致图片拉取不成功
看到的现象就是图片没有显示出来

有时候网络不好,加载超时了,图片也不会显示。
并不是因为这个图片它不可以访问,
同样的图片我们用谷歌浏览器或者其他浏览器去加载,可能就是显示的。


对于网络不好这种情况,我们可以使用image组件的binderror这个事件属性去处理,
监听err事件,当监听到错误以后,我们重新给src属性赋值,一般通过这种方法可以解决,
网络不好加载不出来不显示的问题。

302错误,我们浏览网页的时候,浏览器其实是不断的向服务器发出请求,并且不断的接到服务器的应答,
从而决定下一步去做什么事情,这个应答其实就是状态码,在HTTP协议里面状态码是三位数字,
这个状态码分为5类,分别以12345这五个数字开头的三个数字,
其中302它是服务器返回的一个HTTP状态码,前端想加载A页面,因为网站改版现在不存在了,取而代之的是B页面,
这个时候服务器就可以返回一个302状态码,同时再返回一个B页面的地址,浏览器看到这个状态码和这个地址的时候,
它就会自动的去跳到新的B页面上去。这个就是服务器的302的页面跳转。
涉及到页面跳转的HTTP状态码一共是两个,除了302还有301,这两者都是页面重定向的,
不同的地方在于,
301它是页面永久的转移到了新地址,
302它是请求的网页临时转移到了新网址

我们在微博上经常会看到短链接,其实这种短链接,它就是利用了服务器端的302跳转去实现的,
虽然短链接它有跳转,但是经过测试我们发现目前小程序的image组件也是支持短链接的,
如果给src属性设置一个短链接的图片网址的话,它也是可以加载并且显示的。
微信团队肯定是在内部做了处理的。

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
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> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

scaleToFill 变形
缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素
在这里插入图片描述

aspectFit 上下有灰边
缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。
在这里插入图片描述

aspectFill 被截取
缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。

在这里插入图片描述

背景图片铺满屏幕

最好不用 mode 实现,由wxss样式来实现

在这里插入图片描述

在这里插入图片描述

<!-- 背景图 -->
<view class="container">
</view>

  • 1
  • 2
  • 3
  • 4
/* 背景图样式 */
.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;            
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
2.4 如何剪切图片?
https://github.com/1977474741/image-cropper

它可以让这个图片通过拖拽的方式,然后选择范围,可以任意裁剪。
它的实现原理其实也很简单,它通过四角的一个控制点去控制选择的范围,
四角的控制点它是通过view去渲染出来的,
图片加载完成以后,绘制到canvas画布上,
选定裁剪范围以后,在通过wx.canvasToTempFilePath接口,生成一个临时的图片,
临时图片就是我们需要的裁剪结果。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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 () {

  }
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96

index/index.json

{
  "usingComponents": {
    "image-cropper": "../../components/image-cropper/index"
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
源码
https://github.com/1977474741/image-cropper
  • 1

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
/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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

/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() {
    }
  }
})




  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696
  • 697
  • 698
  • 699
  • 700
  • 701
  • 702
  • 703
  • 704
  • 705
  • 706
  • 707
  • 708
  • 709
  • 710
  • 711
  • 712
  • 713
  • 714
  • 715
  • 716
  • 717
  • 718
  • 719
  • 720
  • 721
  • 722
  • 723
  • 724
  • 725
  • 726
  • 727
  • 728
  • 729
  • 730
  • 731
  • 732
  • 733
  • 734
  • 735
  • 736
  • 737
  • 738
  • 739
  • 740
  • 741
  • 742
  • 743
  • 744
  • 745
  • 746
  • 747
  • 748
  • 749
  • 750
  • 751
  • 752
  • 753
  • 754
  • 755
  • 756
  • 757
  • 758
  • 759
  • 760
  • 761
  • 762
  • 763
  • 764
  • 765
  • 766
  • 767
  • 768
  • 769
  • 770
  • 771
  • 772
  • 773
  • 774
  • 775
  • 776
  • 777
  • 778
  • 779
  • 780
  • 781
  • 782
  • 783
  • 784
  • 785
  • 786
  • 787
  • 788
  • 789
  • 790
  • 791
  • 792
  • 793
  • 794
  • 795
  • 796
  • 797
  • 798
  • 799
  • 800
  • 801
  • 802
  • 803
  • 804
  • 805
  • 806
  • 807
  • 808
  • 809
  • 810
  • 811
  • 812
  • 813
  • 814
  • 815
  • 816
  • 817
  • 818
  • 819
  • 820
  • 821
  • 822
  • 823
  • 824
  • 825
  • 826
  • 827
  • 828
  • 829
  • 830
  • 831
  • 832
  • 833
  • 834
  • 835
  • 836
  • 837
  • 838
  • 839
  • 840
  • 841
  • 842
  • 843
  • 844
  • 845
  • 846
  • 847
  • 848
  • 849
  • 850
  • 851
  • 852
  • 853
  • 854
  • 855
  • 856
  • 857
  • 858
  • 859
  • 860
  • 861
  • 862
  • 863
  • 864
  • 865
  • 866
  • 867
  • 868
  • 869
  • 870
  • 871
  • 872
  • 873
  • 874
  • 875
  • 876
  • 877
  • 878
  • 879
  • 880
  • 881
  • 882
  • 883
  • 884
  • 885
  • 886
  • 887
  • 888
  • 889
  • 890
  • 891
  • 892
  • 893
  • 894
  • 895
  • 896
  • 897
  • 898
  • 899
  • 900
  • 901
  • 902
  • 903
  • 904
  • 905
  • 906
  • 907
  • 908
  • 909
  • 910
  • 911
  • 912
  • 913
  • 914
  • 915
  • 916
  • 917
  • 918
  • 919
  • 920
  • 921
  • 922
  • 923
  • 924
  • 925
  • 926
  • 927
  • 928
  • 929
  • 930
  • 931
  • 932
  • 933
  • 934
  • 935
  • 936
  • 937
  • 938
  • 939
  • 940
  • 941
  • 942
  • 943
  • 944
  • 945
  • 946
  • 947
  • 948
  • 949
  • 950
  • 951
  • 952
  • 953
  • 954
  • 955
  • 956
  • 957
  • 958
  • 959
  • 960
  • 961
  • 962
  • 963
  • 964
  • 965
  • 966
  • 967
  • 968
  • 969
  • 970
  • 971
  • 972
  • 973
  • 974
  • 975
  • 976
  • 977
  • 978
  • 979
  • 980
  • 981
  • 982
  • 983
  • 984
  • 985
  • 986
  • 987
  • 988
  • 989
  • 990
  • 991
  • 992
  • 993
  • 994
  • 995
  • 996
  • 997
  • 998
  • 999
  • 1000
  • 1001
  • 1002
  • 1003
  • 1004
  • 1005
  • 1006
  • 1007
  • 1008
  • 1009
  • 1010
  • 1011
  • 1012
  • 1013
  • 1014
  • 1015
  • 1016
  • 1017
  • 1018
  • 1019
  • 1020
  • 1021
  • 1022
  • 1023
  • 1024
  • 1025
  • 1026
  • 1027
  • 1028
  • 1029
  • 1030
  • 1031
  • 1032
  • 1033
  • 1034
  • 1035
  • 1036
  • 1037
  • 1038
  • 1039
  • 1040
  • 1041
  • 1042
  • 1043
  • 1044
  • 1045
  • 1046
  • 1047
  • 1048
  • 1049
  • 1050
  • 1051
  • 1052
  • 1053
  • 1054
  • 1055
  • 1056
  • 1057
  • 1058
  • 1059
  • 1060
  • 1061
  • 1062
  • 1063
  • 1064
  • 1065
  • 1066
  • 1067
  • 1068
  • 1069
  • 1070
  • 1071
  • 1072
  • 1073
  • 1074
  • 1075
  • 1076
  • 1077
  • 1078
  • 1079
  • 1080
  • 1081
  • 1082
  • 1083
  • 1084
  • 1085
  • 1086
  • 1087
  • 1088
  • 1089
  • 1090
  • 1091
  • 1092
  • 1093
  • 1094
  • 1095
  • 1096
  • 1097
  • 1098
  • 1099
  • 1100
  • 1101
  • 1102
  • 1103
  • 1104
  • 1105
  • 1106
  • 1107
  • 1108
  • 1109
  • 1110
  • 1111
  • 1112
  • 1113
  • 1114
  • 1115
  • 1116
  • 1117
  • 1118
  • 1119
  • 1120
  • 1121
  • 1122
  • 1123
  • 1124
  • 1125
  • 1126

/components/image-cropper/index.json

{
  "component": true
}
  • 1
  • 2
  • 3

/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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/312206
推荐阅读
相关标签
  

闽ICP备14008679号