一、需求场景:
最近闲来无事,boss提出了一个要求,研究相关代码并完成一个关于编辑图片功能的性能优化,该功能的主要界面展示如下:
通过了几分钟的短暂试用,发现就是一个简单的裁剪并保存用户选择并上传的图片作为用户头像的功能。
主要功能点如下:
选择图片并上传。
拖动中间选择区域的位置或者大小选择设置的图片区域。
点击确定按钮,完成图片设置。
初步一看,貌似没有什么值得优化的地方,通过与boss深入了解后知晓,在选择容量较大(超过10MB)的图片时,浏览器响应速度会变得异常缓慢,具体表现是
拖动中间选择区域的位置或者大小时,大约3-4秒才响应一次,严重影响了使用者的用户体验,作为一名专(zhuang)业(bi)的前端人员来说,是完全不能容忍的。
二、性能分析:
作为一名专(zhuang)业(bi)的前端人员来说,遇到问题的首要肯定是先进行性能分析。一般来说浏览器的性能问题无非就是CPU和内存消耗占用。
通过对比查看性能管理器中的chrome内存占用后发现,选择大容量图片前,chrome的内存占用是883MB,而选择大容量图片后,chrome的内存占用达到了2232MB,
内存占用暴涨将近了200%!这就是出现性能问题的所在。事不宜迟,开始着手研究并解决问题。
三、查看代码:
通过查看代码,发现实现该功能的程序猿(也可能是媛)是通过在背景渲染一张完整的image,前景的图片选择区域设置一个透明度为0.3的蒙版(图中的黑色背景区域),
然后在选择区域(图中绿色方框)的背景区域再渲染一张image,再通过动态设置background-position的top、left和size来达到预览并获取选中区域图片的目的。
html结构如下:
< div class="pic_area" style="display:block" ng-show="roomPhotoEdit.file"> < div class="pic_editor"> <!-- upload image --> < span class="pic" id="editing_pic">< br > <!-- 渲染的第一张图片 --> < br > < img ="angular.element(this).scope().roomPhotoEdit.initCrop()" ng-src="{{roomPhotoEdit.file}}" alt="">< br > </ span >< br > <!-- 黑色蒙版背景区域 --> < span class="crop_dimmed"></ span >< br > <!-- 图中绿色方框区域 --> < span class="crop_box" ng-style="roomPhotoEdit.cropLayout" style="top:75px;left:75px">< br > <!-- 渲染的第二张图片 --> < span class="drag_area" ng-style="roomPhotoEdit.edited"></ span > < button class="drag_handle handle_lt" data-type="lt"></ button > < button class="drag_handle handle_rt" data-type="rt"></ button > < button class="drag_handle handle_lb" data-type="lb"></ button > < button class="drag_handle handle_rb" data-type="rb"></ button > </ span > </ div >< br ></ div > |
javascript代码如下(基于AngularJS):
scope.roomPhotoEdit.edited = { 'background-image' : 'url(' + currentImage.attr( 'src' ) + ')' , // 这里多渲染了一张图片 'background-position' : backgroundPositionOption[imageSizeType], // 动态设置top和left 'background-size' : backgroundSizeOption[imageSizeType] // 设置预览区域的尺寸 } |
通过代码可以很明显的看到通过在class为drag_area的区域额外渲染一张背景图片并动态修改background-position和background-size来达到预览选择区域图片的目的。
由于额外渲染了一张图片,如果用户选择的容量超大的图片,则会导致浏览器内存占用暴涨,如果能只渲染一张图片,应该就能极大减少内存占用,可以从此入手。
四、尝试优化:
首先就是去除拖放区域中额外渲染的背景图片。
可以从上图中看到去除了背景图片之后,拖放区域被黑色蒙版遮盖住了,没有预览的效果了。
通过度娘之后得知可以通过动态设置黑色蒙版区域的border-width、border-style、border-color来达到显示拖放区域的预览图片效果。
话不多说,先来直接看看效果。
再来看之前drag_area区域的html结构,可以发现,之前的背景图片等样式全部被去除了。
取而代之的之前的黑色蒙版区域crop_dimmed的样式,可以看到设置了border-width、border-style和border-color,来达到显示中间预览区域的效果。
最后要做的只是,在预览区域的拖放事件中动态去设置drop_dimmed区域的border-width属性即可。
该属性主要的计算规则如下(其中layoutTop为绿色方框区域的top属性,layoutLeft为绿色方框区域的left属性,containerWidth为外部容器的宽度,containerHeight为外部容器的高度,position.size为绿色方框区域的宽高,在这里拖放区域只能等比拖放,所以是个正方形,宽高一致):
border-top-width: layoutTop + "px" border-right-width: containerWidth - layoutLeft - position.size + "px" border-bottom-width: containerHeight - layoutTop - position.size + "px" border-left-width: layoutLeft + "px" |
五、验收
代码改完了最后一步当然是验收了,通过查看修改后的内存占用发现,即使选择了大容量的图片之后,内存占用也稳定在了800MB左右,而且操作比之前流畅多了。
搞定,收工!
六、总结:
从最初的纯粹为了图片裁剪区域的预览,而额外渲染了一张图片开始,到最后的去除额外图片渲染,而采用border-width的方案来达到裁剪区域预览的目的。
前端er们做的事情无非为了用户体验这4个字不断在努力。而前端的一切的方案和手段,最终目的都是为了提升用户体验。
毕竟,我们做出的是最接近用户的产品。