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中的resolvecurrentResolve = 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 flipctx.translate(width, 0);ctx.scale(-1, 1);break;case 3:// 180 rotate leftctx.translate(width, height);ctx.rotate(Math.PI);break;case 4:// vertical flipctx.translate(0, height);ctx.scale(1, -1);break;case 5:// vertical flip + 90 rotate rightctx.rotate(0.5 * Math.PI);ctx.scale(1, -1);break;case 6:// 90 rotate rightctx.rotate(0.5 * Math.PI);ctx.translate(0, -height);break;case 7:// horizontal flip + 90 rotate rightctx.rotate(0.5 * Math.PI);ctx.translate(width, -height);ctx.scale(-1, 1);break;case 8:// 90 rotate leftctx.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```javascriptHandlers.pickImg().then(async canvas => {// canvas 旋转和最大尺寸处理后的canvasif (!!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');})
