h5活动页面结构优化:
概括:
- 修改了标签名称
- 页面节点添加 .page
- 结构优化,主要分为三部分:
- 页面主体部分
- loading部分 因为 刚进页面需要loading
- 其他需要预加载图片的部分
代码实现:
<!--页面主题部分-->
<section id="app">
<!-- p1 start -->
<ul id="p1" class="page" style="position:relative;z-index:2;">
<li class="wpr">
<!-- 内容区域 -->
<header class="head">标题区域</header>
<div class="content">内容区域</div>
<footer class="foot">按钮区域</footer>
</li>
</ul>
<!-- p2 start -->
<ul id="p2" class="page" style="position:relative;z-index:2;">
<li class="wpr">
<!--内容区域-->
<header class="head">上传区域</header>
<div class="content">提示部分区域</div>
<footer class="foot">按钮区域</footer>
</li>
</ul>
<!-- p3 start -->
<ul id="p3" class="page" style="position:relative;z-index:2;">
<li class="wpr">
<!--内容区域-->
<header class="head">展示效果区域</header>
<div class="content">效果切换按钮区域</div>
<footer class="foot">分享保存按钮区域</footer>
</li>
</ul>
</section>
<!--loading部分-->
<section class="loading mask run">
<ul class="loadWpr">
<li class="load">
<img src="./static/img/common/loading.png" class="loadingIcon" alt="">
<var></var>
</li>
</ul>
</section>
<!-- 需要预加载图片部分(包含各种弹窗,结果页的模版)-->
<section class="preLoad">
<!-- 错误提示框 -->
<ul class="mask errMsg" style="display:none;">
<li class="errMsgWpr p-r">
<div class="errMsgIn p-r">
<var></var>
<img src="./static/pic/p4_ok.png" alt="">
</div>
</li>
</ul>
<!--相册相机选择弹窗-->
<ul class="selectBox mask" style="display:none;z-index:590">
<li class="owpr p-r dialog-sty">
<a href="javascript:void(0)" id="cameraBtn">
<img src="./static/pic/cameraBtn.png" alt="">
</a>
<a href="javascript:void(0)" id="galleryBtn">
<img src="./static/pic/albumBtn.png" alt="">
</a>
</li>
</ul>
<!--保存成功toast部分-->
<div class="saveSucToast" style="display:none">保存成功</div>
<!-- input 上传图片 -->
<input type="file" id="inputFile" accept="image/*" style="width:0;height:0;position:absolute;top:0;left:-91846px;" />
<!-- 保存相册相机返回的图片-->
<img id="bridgeImg" style="position:absolute;width:0;height:0;opacity:0.001;top:0;left:-9999px;" alt="" />
<!--cofirm 弹出层-->
<ul class="confirmBox mask">
<li class="comfirm">
<var>是否放弃正在编辑的内容</var>
<img src="static/pic/confirmNo.png" alt="" class="no">
<img src="static/pic/confirmYes.png" alt="" class="yes">
</li>
</ul>
<!--图片保存弹出层-->
<ul class="shareImgBox mask">
<li class="shareInnerBox">
<img alt="">
<p>长按保存图片哦 ~</p>
</li>
</ul>
<!--以下加载的模版图,结果页需要-->
<img src="static/pic/temp3.png" id="res3" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt1.png" id="txt1" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt2.png" id="txt2" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt3.png" id="txt3" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt4.png" id="txt4" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt5.png" id="txt5" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt7.png" id="txt7" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt8.png" id="txt8" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt9.png" id="txt9" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt10.png" id="txt10" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
<img src="static/pic/txt11.png" id="txt11" style="position:absolute;top:-999rem;right:999rem;width:100%;z-index:166;" alt="">
</section>
hide节点内的”图片懒加载”实现
项目的图片 放在 根目录下的 static/img 目录下
如: static/img/1.png
在 页面节点 下要懒加载的图片 书写格式:
<img data-src="img/1.png" alt="">
图片data-src 路径中 不要写 @/static/ ,因为在动态加载图片时 require中写了
<img data-src="img/1.png" alt="">
实现原理:
```javascript // @/static/handlers.js
/**
* 控制display:none 节点中懒加载图片显示
* @param:
* Node 页面节点
* */
static showHideNodeImg(Node) {
imgs = Node.find('img');
if (imgs.eq(0).data('src') && !imgs.eq(0).attr('src')) {
$.each(imgs, function () {
srcRes = $(this).data('src');
srcRes && $(this).attr('src', require(`@/static/${srcRes}`));
})
}
}
<a name="1zWP2"></a>
## 应用:
1. 初始化页面的时候加载图片
```javascript
// @/static/handlers.js
/**
* @param:
* actPage 页面节点
* */
static initPage(actPage) {
return new Promise((resolve) => {
$('.page').css('opacity', 0).hide();
// 加载当前页面图片
actPage.show();
Handlers.showHideNodeImg(actPage);
setTimeout(() => {
actPage.css('opacity', 1).css('willChange', 'auto');
resolve()
}, 100)
})
}
- 加载完首页后,可以加载其他后续需要的图片
async function enterP1(){
await Handlers.initPage($('#p1'));
setTimeout(function () {
Handlers.showHideNodeImg($('.preLoad'))
},200)
}
节点重复解除绑定事件的优化
优化原因和思路
当前:
在函数中给节点绑定事件,会使一个节点多次绑定相似事件,所以目前实现方式是 先解绑事件,然后重新添加
优化:
- 在节点上添加一个标识: havedEvent 值为事件类型字符串如: “click change dbclick”
-
实现
/**
* 判断某个节点是否已绑定指定类型事件,避免函数内绑定事件重复解绑添加事件
* @param:
* Node: 节点
* Type: 事件类型
* @return 如果有
* */
static isHasEvent(Node,Type){
return new Promise((resolve) => {
// havedEvent ,在节点上添加事件绑定的标记
let havedEvent = Node.attr('havedEvent');
if(havedEvent){
if(havedEvent.includes(Type)){
// 已绑定指定事件
resolve(true);
}else{
resolve(false)
}
}else{
resolve(false)
}
})
}
/**
* 判断某个节点是否已绑定指定类型事件,避免函数内绑定事件重复解绑添加事件
* @param:
* Node: 节点
* Type: 事件类型
* @return 如果有
* */
static addEvent(Node,Type){
let havedEvent = Node.attr('havedEvent');
if(havedEvent && havedEvent.includes(Type)){
throw new Error(`重复标记了${Type}事件类型`);
}
if(havedEvent && !havedEvent.includes(Type)){
Node.attr('havedEvent',`${havedEvent} ${Type}`)
}else{
Node.attr('havedEvent',Type)
}
}
应用:
// 如首页按钮点击进入上传页
async function enterP1() {
// 先获取节点,避免多次获取同一个节点
let $startBtn = $('.startBtn');
// 判断节点上是否已绑定click事件,没有则绑定
if(! await Handlers.isHasEvent($startBtn,'click')){
// 绑定时给节点添加标识
Handlers.addEvent($startBtn,'click')
$startBtn.on('click', function(){
// 代码实现
enterP2()
})
}
}
获取APP信息
概括:
-
实现:
static checkAppInfo() {
return new Promise((resolve)=>{
if (BrowserChecker.isIos()) {
assignMyApp({isIos: true})
} else if (BrowserChecker.isAndroid()) {
assignMyApp({isAnd: true})
}
Bridge.appInfo(res => {
if (res.app) {
let o = {
isInApp: true,
version: res.app,
inState: '-inApp'
};
// 如果有版本,且小于3.3.5的
if (!compareVersion('3.3.5', res.app)) {
o.needUpdata = true;
}
assignMyApp(o);
assignMyApp({EventFullPath: eventBaseName + Handlers.myApp.inState});
EventFullPath = Handlers.myApp.EventFullPath;
console.log('在站内');
}
resolve();
})
})
}
应用:
Handlers.checkAppInfo().then(()=>{
console.log('站内')
$('.saveInApp').css('display', 'block');
$('.saveOutApp').css('display', 'none');
_hmt.push(['_trackEvent', Handlers.myApp.EventFullPath, 'init', '受访pv'])
});
初始化页面:
概述:
-
实现:
/**
* @param:
* actPage 页面节点
* */
static initPage(actPage) {
return new Promise((resolve) => {
$('.page').css('opacity', 0).hide();
actPage.show();
// 加载当前页面
Handlers.showHideNodeImg(actPage);
setTimeout(() => {
actPage.css('opacity', 1).css('willChange', 'auto');
resolve()
}, 100)
})
}
应用:
async function enterP1() {
await Handlers.initPage($('#p1'));
}
图片上传到显示
优化:
- 修改为Promise语法
- 避免多次获取节点
- 避免了节点解绑和重新绑定
- 兼容处理旋转图片
代码实现:
获取图片部分
let currentResolve; // 记录 相册、相机按钮点击时执行函数中的 回调函数参数
// 选取照片
static pickImg() {
return new Promise(async resolve => {
// 动态赋值最新的resolve,这样“相机相册按钮”节点执行click事件中,获取的是最新Promise中的resolve
currentResolve = resolve;
if (Handlers.myApp.isInApp) {
cameraMenu();
//避免多次获取节点
let $galleryBtn = $("#galleryBtn"), $cameraBtn = $('#cameraBtn');
// 避免了节点解除和重新绑定事件
if(! await Handlers.isHasEvent($galleryBtn,'click')){
Handlers.addEvent($galleryBtn,'click');
$galleryBtn.off('click').on('click', () => {
loading();
cameraMenu({state: 0});
const galleryParams = new EventCameraParam({type: 'imageAlbum'})
_hmt.push(['_trackEvent', EventFullPath, 'Btn', '相册选取照片'])
Bridge.eventCamera(galleryParams, function (res, type) {
// currentResolve 最新的promise中的resolve
// 站内获取图片后的操作
Handlers.eventCameraCallback(res, type, currentResolve)
})
})
}
// 避免了节点解除和重新绑定事件
if(! await Handlers.isHasEvent($cameraBtn,'click')){
Handlers.addEvent($cameraBtn,'click');
$cameraBtn.off('click').on('click', () => {
loading();
cameraMenu({state: 0})
setTimer = setTimeout(() => {
loading({state: 0});
}, 1200)
const cameraParams = new EventCameraParam({type: 'imageCamera'})
_hmt.push(['_trackEvent', EventFullPath, 'Btn', '拍照选取照片'])
Bridge.eventCamera(cameraParams, function (res, type) {
// currentResolve 最新的promise中的resolve
// 站内获取图片后的操作
Handlers.eventCameraCallback(res, type, currentResolve)
})
})
}
} else {
/*
站外input如果没有选取照片,无法捕捉onchange事件回调,不要提前加loading效果
*/
let $inputFile = $('#inputFile') ;
$inputFile[0].value = '';
$inputFile.trigger('click');
// 避免了节点解除和重新绑定事件
if(! await Handlers.isHasEvent($inputFile,'change')){
Handlers.addEvent($inputFile,'change');
$('#inputFile').on('change', function () {
// currentResolve 最新的promise中的resolve
// 站外获取图片后的操作
return Handlers.fileChanged.call(this, currentResolve)
})
}
}
})
}
上面代码为什么要用一个变量(currentResolve)呢 ?
- 当我们第二次调用 pickImg 方法时 ,产生新的Promise,新的resolve,但是 “相册相机按钮”节点已经绑定过“click,change”事件了,所以不再重新绑定。
- 这样在事件绑定函数中传入的回调函数参数(resolve),还是第一次传入的resolve,导致第二次调用 pickImg 没有返回结果,也就是 Handler.pickImg() 后面的then不再执行
- 如果我们每次调用将最新的resolve赋值给一个变量 currentResolve,事件回调函数执行时,将currentResolve当作回调函数的参数传下去,执行完调用currentResolve返回结果。
- 通过上一步的赋值操作,使的 pickImg 都有返回值,在then中获取
站内获取图片后操作
/*
站内 拍照、选图的回调
*/
static eventCameraCallback(res, type, resolve){
if (res.success) {
let img = new Image()
img.onload = function () {
let imageType = this.src.split(",")[0].split(";")[0].split(":")[1].toLowerCase();
let imageTypeCheck = imageType.includes("jpg") || imageType.includes("jpeg") || imageType.includes("png")
if (!imgSupport.validationImageSize(this.width, this.height)) {
clearTimeout(setTimer);
return resolve({errMsg: '图片过小,请重选照片'})
} else if (!imageTypeCheck) {
clearTimeout(setTimer);
return resolve({errMsg: '图片格式不符合'})
}
// 旋转图片的浏览器兼容处理,合理限制图片大小不操过3000 ,下文展开
// Handlers.renderFileChangedImg
}
img.src = res.base64Image;
img.onerror = function () {
return resolve({errMsg: '请检查网络连接'})
}
} else {
resolve()
}
}
站外获取图片后操作
// 站外 选图的回调
static fileChanged(resolve){
loading()
if (this.files.length <= 0){
return resolve()
}
let img = new Image()
img.onload = function () {
if (!imgSupport.validationImageSize(this.width, this.height)) {
return resolve({errMsg: '图片尺寸或比例不符合'})
}
// 旋转图片的浏览器兼容处理,合理限制图片大小不操过3000 ,下文展开
// Handlers.renderFileChangedImg
}
img.src = imgSupport.inputPath2url(this.files[0])
}
获取后图片旋转缩放处理
图片旋转判断实现
/**
* 原理是:原图的是一张 1 * 2 ,oritation为6的图片(逆时针旋转90deg)的图片,
* ios高 获取的图片尺寸 width为2,height为1,
* ios低和android获取的图片尺寸 width为1,height为2,实现代码如下
* 用一张特殊的图片来检测当前浏览器是否对带 EXIF 信息的图片进行回正
* */
static detectImageAutomaticRotation() {
const testAutoOrientationImageURL =
'data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAA' +
'AAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA' +
'QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' +
'BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/x' +
'ABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAA' +
'AAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==';
let isImageAutomaticRotation;
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
// 如果图片变成 1x2,说明浏览器对图片进行了回正
isImageAutomaticRotation = img.width === 1 && img.height === 2;
resolve(isImageAutomaticRotation);
};
img.src = testAutoOrientationImageURL;
});
}
图片旋转的浏览器兼容处理
- 判断浏览器是否对旋转图片已纠正
对图片进行纠正
// 处理旋转图片的兼容处理 、 图片渲染为合适尺寸
static renderFileChangedImg(img, distImg) {
return new Promise(function (resolve) {
Handlers.detectImageAutomaticRotation().then(res=>{
// res 为 true 则浏览器对图片进行了回正,canvas操作的是纠正后的图片
// 这里 android没进行旋转修复,是因为foodie android的jsBridge返回的图片
// 获取不到旋转信息,不执行getData的回调函数
if(res || BrowserChecker.isAndroid()){
// Handlers.fixedImgOAS 设置图片尺寸,下文解析
Handlers.fixedImgOAS(img, {
maxWidth: 3000,
maxHeight: 3000,
}).then((canvas)=>{
resolve(canvas)
})
}else{
EXIF.getData(img, () => {
var allMetaData = EXIF.getAllTags(img);
var orientation = allMetaData.Orientation;
// Handlers.fixedImgOAS 设置图片尺寸,下文解析
Handlers.fixedImgOAS(img, {
maxWidth: 3000,
maxHeight: 3000,
orientation: orientation
}).then((canvas)=>{
resolve(canvas)
})
})
}
})
})
}
上面代码中 android没进行旋转修复,是因为foodie android的jsBridge返回的图片获取不到旋转信息,不执行getData的回调函数
纠正旋转,调整图片尺寸
```javascript /**
- 原名:fixedImageOritationAndSize
- @param image 已加载的图片原生节点
- @param options 配置 可选参数 width,height,maxWidth,maxHeight,oritation
@return {Promise
options = options || {};
// 通过options 中的width,height,maxWidth,maxHeight,及图片的原始尺寸, 获取最终的width,height 值
let imgWidth = image.naturalWidth, imgHeight = image.naturalHeight,
width = options.width, height = options.height,
maxWidth = options.maxWidth, maxHeight = options.maxHeight;
if (width && !height) {
height = (imgHeight * width / imgWidth) << 0;
} else if (height && !width) {
width = (imgWidth * height / imgHeight) << 0;
} else {
width = imgWidth;
height = imgHeight;
}
if (maxWidth && width > maxWidth) {
width = maxWidth;
height = (imgHeight * width / imgWidth) << 0;
}
if (maxHeight && height > maxHeight) {
height = maxHeight;
width = (imgWidth * height / imgHeight) << 0;
}
var opt = {};
for (var k in options) opt[k] = options[k];
opt.width = width;
opt.height = height;
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
ctx.save();
// 设置canvas 的尺寸并旋转偏移canvas,下边附有代码
transformCoordinate(canvas, ctx, width, height, options.orientation);
ctx.drawImage(image,0,0,imgWidth,imgHeight,0,0,width,height);
ctx.restore();
resolve(canvas);
}) }
/**
- 根据指定的尺寸和方向变换画布
- @param canvas
- @param ctx
- @param width
- @param height
- @param orientation
*/
function transformCoordinate(canvas, ctx, width, height, orientation) {
switch (orientation) {
} switch (orientation) {case 5:
case 6:
case 7:
case 8:
canvas.width = height;
canvas.height = width;
break;
default:
canvas.width = width;
canvas.height = height;
} } ```case 2:
// horizontal flip
ctx.translate(width, 0);
ctx.scale(-1, 1);
break;
case 3:
// 180 rotate left
ctx.translate(width, height);
ctx.rotate(Math.PI);
break;
case 4:
// vertical flip
ctx.translate(0, height);
ctx.scale(1, -1);
break;
case 5:
// vertical flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.scale(1, -1);
break;
case 6:
// 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(0, -height);
break;
case 7:
// horizontal flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(width, -height);
ctx.scale(-1, 1);
break;
case 8:
// 90 rotate left
ctx.rotate(-0.5 * Math.PI);
ctx.translate(-width, 0);
break;
default:
break;
应用:
$('.reUpBtn, .selectArea').on('click',()=>{
Handlers.pickImg()
.then(async img => {
if (!!img == false) {
return loading({state: 0})
} else if (img.errMsg) {
return errMsg({text: img.errMsg})
}
// 拿到获取到的图片
uploadFlag = true; // 点击上传按钮,可以上传图片了
let $dragImg = $('.dragImg'),dragImg = $dragImg[0]; // dragImg 进行拖动缩放移动操作的图片
// 图片附值
canvas.toBlob((blob)=>{
let url = URL.createObjectURL(blob);
dragImg.src = url;
dragImg.onload = function () {
URL.revokeObjectURL(url);
// 图片 拖拽操作实现,下文讲解
await Handlers.ezGesture($('.showPicArea'),$dragImg,$('.selectArea'));
$('.dragArea').css('opacity', 1)
loading({state: 0})
}
})
$('.dragTip').css('display','none'); // ➕号按钮隐藏
})
})
图片拖拽缩放操作
概述:
- 图片加载完后,立刻居中显示
- 去掉了配置,targetMinWidth,targetMinHeight设置为操作层的尺寸
- EZGesture库,将containerDom(操作层)的尺寸内置在构造函数中,位置改变通过transform来实现,代替position,因为top和left的改变会触发浏览器的 reflow和 repaint 。然后整个动画过程都在不断触发浏览器的重新渲染,这个过程是很影响性能的。
transform
动画由GPU控制,支持硬件加速,不重新绘制。(https://zhuanlan.zhihu.com/p/78230297) - 原来的定位实现是相对于.page节点的,如果不小心给其他父容器设置position值,会影响实现。
实现:
```javascript /**- 给图片绑定缩放和平移操作
- @param $showPicArea // 操作平面层
- @param $dragImg // 操作的图片
*/
static ezGesture($showPicArea,$dragImg){
let dragImgRatio = ($dragImg.width() / $dragImg.height()).toFixed(3);
//clientWidth 获取节点除边框外的尺寸,没有单位度量
let showPicAreaRatio = ($showPicArea[0].clientWidth / $showPicArea[0].clientHeight).toFixed(3);
// cropGesture && cropGesture.unbindEvents(); //取消上次的监听
if(dragImgRatio > showPicAreaRatio){
}else{$dragImg.css({'width': 'auto', 'height': '100%'});
setTimeout(function () {
$dragImg.css({'transform':`translate(${-($dragImg.width() - $showPicArea[0].clientWidth)>>1}px,0)`});
},50)
} new EZGesture($showPicArea[0],$dragImg[0]); }$dragImg.css({'height': 'auto', 'width': '100%'});
setTimeout(function () {
$dragImg.css({'transform':`translate(0,${-($dragImg.height() - $showPicArea[0].clientHeight)>>1}px)`});
},50)
//EZGesture库: // 手势框 // 不要给drop图片加transition /**
- pageX,pageY 相对于HTML文档的距离
- clientX,clientY 返回触点相对于可见视区(visual viewport)左边沿的的X坐标。不包括任何滚动偏移.这个值会根据用户对可见视区的缩放行为而发生变化.
- screenX,screenY 触点相对于屏幕左边沿的X坐标,不包含页面滚动的偏移量
- element.getBoundingClientRect() 方法返回元素的大小及其相对于可见视区的位置。
*/ (function(){ let supportTouch = (“ontouchend” in document); // let supportTouch = (‘createTouch’ in document); // 阻止事件冒泡 function preventEventPropagation(evt) {
evt.preventDefault();
evt.stopPropagation();
return false;
}
/**
- 获取节点的transform值
- @param Node
@return {{translate: {x: number, y: number}, scale: number}} */ function _getTransform(Node) { let str = Node.style.transform; str = str.trim(); let arr = str.split(‘) ‘); let obj = {translate:{x:0,y:0},scale:1}; for(let i =0,j = arr.length; i <j ; i++){
if(arr[i].trim().startsWith('translate')){
let pos = arr[i].split('(')[1].split(',');
obj.translate = {
x:parseFloat(pos[0]),
y:parseFloat(pos[1])
}
}else if(arr[i].trim().startsWith('scale')){
let sca = arr[i].split('(')[1];
obj.scale = parseFloat(sca)
}
} return obj }
// 手势开始触发 function gestureTouchStart(evt) { let touches = evt.touches || evt.originalEvent.touches; let touch = touches[0]; // 获取第一个点 相对于 HTML文档左上角的位置 let offset = {
'x': touch.clientX,
'y': touch.clientY
}; // 如果触摸点 大于等于 2指 if (touches.length >= 2) {
// 获取第二点的
let touch2 = touches[1];
// 获取第二个点 相对于 HTML文档左上角的位置
let offset2 = {
'x': touch2.clientX,
'y': touch2.clientY
};
this.gesturePinchStart([offset, offset2]);
} else {
this.gesturePanStart(offset);
}
return preventEventPropagation(evt); } // 手势移动 function gestureTouchMove(evt) { let touches = evt.touches || evt.originalEvent.touches; let touch = touches[0]; // 平移过程中第一个点在可视区域的坐标 let offset = {
'x': touch.clientX,
'y': touch.clientY
};
if (touches.length >= 2) {
// 如果大于一个触摸点,则获取第二个点坐标
let touch2 = touches[1];
let offset2 = {
'x': touch2.clientX,
'y': touch2.clientY
};
this.gesturePinchChange([offset, offset2]);
} else {
this.gesturePanMove(offset);
}
return preventEventPropagation(evt); } // 手势结束 function gestureTouchEnd(evt) { this.gesturePanEnd(); this.gesturePinchEnd(); return preventEventPropagation(evt); } // 鼠标开始点击 function gestureMouseDown(evt) { let offset = {
'x': evt.clientX,
'y': evt.clientY
}; this.gesturePanStart(offset);
return preventEventPropagation(evt); } // 鼠标移动 function gestureMouseMove(evt) { let offset = {
'x': evt.clientX,
'y': evt.clientY
}; this.gesturePanMove(offset); return preventEventPropagation(evt); } // 鼠标抬起 function gestureMouseUp(evt) { this.gesturePanEnd(); return preventEventPropagation(evt); } // 平移开始 offset 触摸点在页面坐标的位置 function gesturePanStart(offset) {
// 禁止缩放过程操作 this.gesturePinchEnabled = false; // 记录开始坐标点 this.gesturePanFrom = offset;
let transform = _getTransform(this.targetDom); this.gesturePanOrigin.x = transform.translate.x; this.gesturePanOrigin.y = transform.translate.y;
// 开启移动操作 this.gesturePanEnabled = true;
return false; } // 手势平移过程 function gesturePanMove(offset) { // offset 获取移动过程中点在可视区域的坐标 if (this.gesturePanEnabled) {
let targetOriginX = ~~(this.gesturePanOrigin.x + offset.x - this.gesturePanFrom.x);
let targetOriginY = ~~(this.gesturePanOrigin.y +offset.y - this.gesturePanFrom.y);
this.targetDom.style.transform = `translate(${targetOriginX}px,${targetOriginY}px)`;
} return false; } // 手势平移结束 function gesturePanEnd() { // $(‘.selectArea’).css(‘opacity’, 0) if (this.gesturePanEnabled) {
let targetRect = this.targetDom.getBoundingClientRect();
let targetOriginX = targetRect.left - this.containerRect.left;
let targetOriginY = targetRect.top - this.containerRect.top;
// 如果图片距离框左边有空白区域
if (targetOriginX > 0) {
targetOriginX = 0;
} else {
let targetWidth = targetRect.width;
let containerWidth = this.containerRect.width;
// 在图片左边隐藏的条件下,如果 右边有空白区域
if ((targetOriginX + targetWidth) < containerWidth) {
targetOriginX = containerWidth - targetWidth;
}
}
if (targetOriginY > 0) {
targetOriginY = 0;
} else {
let targetHeight = targetRect.height;
let containerHeight = this.containerRect.height;
if ((targetOriginY + targetHeight) < containerHeight) {
targetOriginY = containerHeight - targetHeight;
}
}
this.targetDom.style.transform = `translate(${targetOriginX}px,${targetOriginY}px)`;
this.gesturePanEnabled = false;
}
return false;
}
// 手势缩放开始
function gesturePinchStart(offsets) {
// 禁用平移功能
this.gesturePanEnabled = false;
// 两个点 X轴方向的距离, Y轴方向的距离
let distanceX = Math.abs(offsets[1].x - offsets[0].x);
let distanceY = Math.abs(offsets[1].y - offsets[0].y);
// 两个触摸点的长度值
this.gesturePinchFrom = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
if (this.gesturePinchFrom > 0) {
// 获取拖拽元素的尺寸
let targetRect = this.targetDom.getBoundingClientRect();
let centerX = (offsets[0].x + offsets[1].x) * 0.5 - targetRect.left;
let centerY = (offsets[0].y + offsets[1].y) * 0.5 - targetRect.top;
// 缩放中心点在原图中的比例
this.gesturePinchOrigin.x = centerX / targetRect.width;
this.gesturePinchOrigin.y = centerY / targetRect.height;
this.gesturePinchSize.width = targetRect.width;
this.gesturePinchSize.height = targetRect.height;
this.gesturePinchEnabled = true;
}
return false;
}
// 手势缩放过程
//
function gesturePinchChange(offsets) {
if (this.gesturePinchEnabled) {
// 两个点 X轴方向的距离, Y轴方向的距离
let distanceX = Math.abs(offsets[1].x - offsets[0].x);
let distanceY = Math.abs(offsets[1].y - offsets[0].y);
// 触摸过程中,两个触摸点的距离
let gesturePinchTo = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
// 缩放的比例
let scale = gesturePinchTo / this.gesturePinchFrom;
// 设置大小
let targetWidth = ~~ this.gesturePinchSize.width * scale;
let targetHeight = ~~ this.gesturePinchSize.height * scale;
// 获取移动过程中的中心点
let centerX = ~~((offsets[0].x + offsets[1].x) * 0.5 - this.containerRect.left);
let centerY = ~~((offsets[0].y + offsets[1].y) * 0.5 - this.containerRect.top);
let targetOriginX = ~~((centerX - targetWidth * this.gesturePinchOrigin.x));
let targetOriginY = ~~((centerY - targetHeight * this.gesturePinchOrigin.y));
this.targetDom.style.width = `${targetWidth}px`;
this.targetDom.style.height = `${targetHeight}px`;
this.targetDom.style.transform = `translate(${targetOriginX}px,${targetOriginY}px)`;
}
return false;
}
// 手势缩放过程
function gesturePinchEnd() {
if (this.gesturePinchEnabled) {
let targetRect = this.targetDom.getBoundingClientRect();
let targetOriginX = targetRect.left - this.containerRect.left;
let targetOriginY = targetRect.top - this.containerRect.top;
let targetWidth = targetRect.width;
let targetHeight = targetRect.height;
let dragImgRatio = (targetWidth / targetHeight).toFixed(3);
//clientWidth 获取节点除边框外的尺寸,没有单位度量
let showPicAreaRatio = (this.containerDom.clientWidth / this.containerDom.clientHeight).toFixed(3);
if(dragImgRatio > showPicAreaRatio){
if(targetHeight < this.containerDom.clientHeight){
targetHeight = this.containerDom.clientHeight;
targetWidth = ~~(targetHeight * dragImgRatio);
targetOriginX = -(targetWidth - this.containerDom.clientWidth)>>1;
targetOriginY = 0;
}
} else {
if(targetWidth < this.containerDom.clientWidth){
targetWidth = this.containerDom.clientWidth;
targetHeight = ~~(targetWidth / dragImgRatio);
targetOriginX = 0;
targetOriginY = -(targetHeight - this.containerDom.clientHeight)>>1;
}
}
if (targetOriginX > 0) {
targetOriginX = 0;
} else {
let containerWidth = this.containerRect.width;
if ((targetOriginX + targetWidth) < containerWidth) {
targetOriginX = containerWidth - targetWidth;
}
}
if (targetOriginY > 0) {
targetOriginY = 0;
} else {
let containerHeight = this.containerRect.height;
if ((targetOriginY + targetHeight) < containerHeight) {
targetOriginY = containerHeight - targetHeight;
}
}
this.targetDom.style.width = `${targetWidth}px`;
this.targetDom.style.height = `${targetHeight}px`;
this.targetDom.style.transform = `translate(${targetOriginX}px,${targetOriginY}px)`;
this.gesturePinchEnabled = false;
}
return false;
}
// 绑定事件
function bindEvents() {
let self = this;
if (supportTouch) {
self.containerDom.ontouchstart = function(evt){ self.gestureTouchStart(evt); return preventEventPropagation(evt); };
self.containerDom.ontouchmove = function(evt){ self.gestureTouchMove(evt); return preventEventPropagation(evt); };
self.containerDom.ontouchend = function(evt){ self.gestureTouchEnd(evt); return preventEventPropagation(evt); };
self.containerDom.ontouchcancel = function(evt){ self.gestureTouchEnd(evt); return preventEventPropagation(evt); };
} else {
self.containerDom.onmousedown = function(evt){ self.gestureMouseDown(evt); return preventEventPropagation(evt); };
self.containerDom.onmousemove = function(evt){ self.gestureMouseMove(evt); return preventEventPropagation(evt); };
self.containerDom.onmouseup = function(evt){ self.gestureMouseUp(evt); return preventEventPropagation(evt); };
self.containerDom.onmouseout = function(evt){ self.gestureMouseUp(evt); return preventEventPropagation(evt); };
}
}
// 解绑事件
function unbindEvents() {
let self = this;
if (supportTouch) {
self.containerDom.ontouchstart = null;
self.containerDom.ontouchmove = null;
self.containerDom.ontouchend = null;
} else {
self.containerDom.onmousedown = null;
self.containerDom.onmousemove = null;
self.containerDom.onmouseup = null;
}
}
// 手势类
let EZGestureClass = function(containerDom, targetDom){
this.containerDom = containerDom;
this.targetDom = targetDom;
this.gesturePanEnabled = false; // 平移开关
this.gesturePanFrom = {x:0, y:0}; // 平移开始时,触摸点在浏览器可视区域的位置
this.gesturePanOrigin = {x:0, y:0};// 平移开始时,原来的位置
this.gesturePinchEnabled = false; // 缩放开关
this.gesturePinchFrom = 0; //开始缩放,两个触摸点的距离
this.gesturePinchOrigin = {x:0, y:0}; // 图片再缩放前,缩放中心点在图片中的比例
this.gesturePinchSize = {width:0, height:0}; // 开始缩放时 targetDom 的尺寸
this.containerRect = this.containerDom.getBoundingClientRect(); // containerDom 的尺寸
this.bindEvents();
}
EZGestureClass.prototype.gestureTouchStart = gestureTouchStart;
EZGestureClass.prototype.gestureTouchMove = gestureTouchMove;
EZGestureClass.prototype.gestureTouchEnd = gestureTouchEnd;
EZGestureClass.prototype.gestureMouseDown = gestureMouseDown;
EZGestureClass.prototype.gestureMouseMove = gestureMouseMove;
EZGestureClass.prototype.gestureMouseUp = gestureMouseUp;
EZGestureClass.prototype.gesturePanStart = gesturePanStart;
EZGestureClass.prototype.gesturePanMove = gesturePanMove;
EZGestureClass.prototype.gesturePanEnd = gesturePanEnd;
EZGestureClass.prototype.gesturePinchStart = gesturePinchStart;
EZGestureClass.prototype.gesturePinchChange = gesturePinchChange;
EZGestureClass.prototype.gesturePinchEnd = gesturePinchEnd;
EZGestureClass.prototype.bindEvents = bindEvents;
EZGestureClass.prototype.unbindEvents = unbindEvents;
window.EZGesture = EZGestureClass;
})();
<a name="oRJk8"></a>
## 应用:
- 拿到图片后,赋值给dragImg,onload之后,执行Handlers.ezGesture
```javascript
Handlers.pickImg()
.then(async canvas => {
// canvas 旋转和最大尺寸处理后的canvas
if (!!canvas == false) {
return loading({state: 0})
} else if (canvas.errMsg) {
return errMsg({text: canvas.errMsg})
}
// 点击上传按钮,可以上传图片了
uploadFlag = true;
let $dragImg = $('.dragImg'),dragImg = $dragImg[0];
canvas.toBlob((blob)=>{
let url = URL.createObjectURL(blob);
dragImg.src = url;
dragImg.onload = async function () {
URL.revokeObjectURL(url);
Handlers.ezGesture($('.selectArea'),$dragImg);
$('.dragArea').css('opacity', 1)
loading({state: 0})
}
})
$('.dragTip').css('display','none');
})