关于移动端web:移动端架构师完结MK

download:挪动端架构师【完结】Python字典内置函数和方法: 注:使用了 items、values、keys 返回的是可迭代对象,可能使用 list 转化为列表。 len(字典名): 返回键的个数,即字典的长度 len(字典名):返回键的个数,即字典的长度dic = {'a':123,'b':456,'c':789,'d':567}print(len(dic)) 4str(字典名): 将字典转化成字符串 str(字典名):将字典转化成字符串dic = {'a':123,'b':456,'c':789,'d':567}print(str(dic)) {'a': 123, 'b': 456, 'c': 789, 'd': 567}type(字典名): 查看字典的类型 type(字典名):查看字典的类型dic = {'a':123,'b':456,'c':789,'d':567}print(type(dic)) <class 'dict'>复制代码内置方法: clear( ): 删除字典内所有的元素 复制代码 clear( ):删除字典内所有的元素dic = {'a':123,'b':456,'c':789,'d':567}dic.clear()print(dic) {}复制代码copy( ): 浅拷贝一个字典 复制代码 copy( ):浅拷贝一个字典dic = {'a':123,'b':456,'c':789,'d':567}dic_two = dic.copy()print(dic) {'a': 123, 'b': 456, 'c': 789, 'd': 567}print(dic_two) {'a': 123, 'b': 456, 'c': 789, 'd': 567}复制代码fromkeys(seq[,value]): 创建一个新字典,seq作为键,value为字典所有键的初始值(默认为None) 复制代码 fromkeys(seq[,value]):创建一个新字典,seq作为键,value为字典所有键的初始值(默认为None)dic = dict.fromkeys('abcd') ...

November 17, 2021 · 1 min · jiezi

移动Web深度剖析

随着前端技术的急速发展,随着互联网行业的日益发展,HTML5作为一种比较新型的开发技术早已经被很多大的企业所应用,通过HTML5语言可以开发适用于任何设备上的酷炫网站页面,所以HTML5的发展趋势可想而知。话说HTML5退出了也好长一段时间了,现在还拿出来炒冷饭O(∩_∩)O哈哈~ HTML5与SEO为了更好地处理今天的互联网应用,HTML5添加了很多新元素及功能,比如:图形的绘制,多媒体内容,更好的页面结构,更好的形式处理,和几个Api拖放元素,定位,包括网页 应用程序缓存,存储,网络工作者。HTML5推出一个很重要的概念就是语义化标签。这一概念给网页的SEO带来了很大的帮助。 使搜索引擎更加容易抓取和索引 对于一些网站,特别是那些严重依赖于FLASH的网站HTML5是一个大福音。如果你有一个都是FLASH的站点,你就一定会看到切换到HTML5的好处。首先,搜索引擎的蜘蛛将能够抓取你的站点和索引你的内容。所有嵌入到动画中的内容将全部可以被搜索引擎读取。在搜索引擎优化的基本理论中,这一方面将会驱动你的网站获得更多的右击流量。 提供更多的功能,提高用户的友好体验 使用HTML5的另一个好处就是它可以增加更多的功能。对于HTML5的功能性问题,我们从全球几个主流站点对它的青睐就可以看出。社交网络大亨Facebook已经推出他们期待已久的基于HTML5的iPad应用平台,潘多拉也推出他们基于HTML5的音乐播放器的新版本。游戏平台Zynga也在推出了三款新的在移动设备浏览器上运行的基于HTML5的游戏等等。每天都有不断的基于HTML5的网站和HTML5特性的网站被推出。保持站点处于新技术的前沿,也可以很好的提高用户的友好体验。 可用性的提高,提高用户的友好体验 HTML5的推出给前端行业带来了一片新的天空,不单单提供了大量的API,给移动端开发也是一个很大的福音。 说了这么多,扯了这么多,那么上面这些和移动端又有什么关系。实质上是没有关系的,在做移动端开发,由于移动端对于HTML5的支持还是很不错的。推荐大家在做移动端开发的时候,尽量使用HTML5新添加的那些语义化的标签。 HTML5在特别老的手机上会有问题,因为手机是无法识别这些新标签的。所以我们需要使用JavaScript的createElement方法,手动创建标签,以解决兼容问题,不做多余赘述,这不是本文的重点。 meta标签看到meta标签,不禁的让我想起一次面试经历,面试官当时问了我一个问题<head>里面都有什么?记得当时只是回答了都有哪些标签,然而,面试官想要知道的不只是简简单单的几个标签。meta一个熟悉既陌生的标签。它到底能做什么? META标签:通常所说的meta标签,是在HTML网页源代码中一个重要的html标签。META标签用来描述一个HTML网页文档的属性,例如作者、日期和时间、网页描述、关键词、页面刷新等。 根据百度百科介绍,可以做很多事情的ing,有的时候SEO也是依赖于meta标签。元数据是用来概括描述数据的一些基本数据。也就是描述数据的数据。 SEOmeta标签共有两个属性,分别是http-equiv属性和name属性。meta标签用来描述一个HTML网页文档的属性,但却是文档的最基本的元数据 name name属性主要用于描述网页,与之对应的属性值为content,content中的内容主要是便于搜索引擎机器人查找信息和分类信息用的。 meta标签的name属性语法格式是:<meta name="参数" content="具体的参数值" />。 其中name属性主要有以下几种参数: 名称作用举例Keywords(关键字)keywords用来告诉搜索引擎你网页的关键字<meta name ="keywords" content="science,human">description(网站内容描述)description用来告诉搜索引擎你的网站主要内容<meta name="description" content="this's Aaron blog!">author(作者)标注网页的作者<meta name="author" content"root,wo_99936@qq.com">generator(页面生成器)规定用于生成文档的一个软件包(不用于手写页面)<meta name="generator" content="FrontPage 4.0">revised(页面修改信息)这常用于最后更改的网站<meta name="revised" content="story,2015/07/22" />copyright(版权信息)版权信息<meta name="copyright" content="All Rights Reserved" />http-equiv http-equiv顾名思义,相当于http协议中文件头的作用,它可以向浏览器传回一些有用的信息,以帮助正确和精确地显示网页内容,与之对应的属性值为content,content中的内容其实就是各个参数的变量值。 名称作用举例content-Type(显示字符集的设定)设定页面使用的字符集<meta http-equiv="content-Type" content="text/html; charset=gb2312"/>Expires(期限)可以用于设定网页的到期时间。一旦网页过期,必须到服务器上重新传输,这里必须使用GMT的时间格式<meta http-equiv="expires" content="Fri, 12 Jan 2001 18:18:18 GMT">Pragma(cache模式)禁止浏览器从本地计算机的缓存中访问页面内容<meta http-equiv="Pragma" content="no-cache"/>Refresh(刷新)自动刷新并指向新页面,其中的2是指停留2秒钟后自动刷新到URL网址<meta http-equiv="Refresh" content="2; URL=http://www.root.net&quot;/>Set-Cookie(cookie设定)设置cookie, 如果网页过期,那么存盘的cookie将被删除<meta http-equiv="Set-Cookie" content="cookievalue=xxx; expires=Friday, 12-Jan-2001 18:18:18 GMT; path=/";/>Window-target(显示窗口的设定)强制页面在当前窗口以独立页面显示,用来防止别人在框架里调用自己的页面<meta http-equiv="Window-target" content="_top"/>meta标签的一个很重要的功能就是设置关键字,来帮助你的主页被各大搜索引擎登录,提高网站的访问量。在这个功能中,最重要的就是对Keywords和description的设置。因为按照搜索引擎的工作原理,搜索引擎首先派出机器人自动检索页面中的keywords和decription,并将其加入到自己的数据库,然后再根据关键词的密度将网站排序。 移动端辅助参数HTML5推出之后又给meta赋予了新的使命。meta可以辅助对移动段适配提供一些参数,供浏览器使用。 // 标签的 name 是:可视区域窗口name = "viewport"// 设置可视区内容的属性content// 宽度等于设备的宽度;一般情况下 width 可以接受两种参数(number||device-width)// 由于 number [任意数值]在某些移动设备的兼容性不好,所以一般都会使用 device-width。width="device-width"// 页面初始比例,配合缩放最大最小使用(number)initial-scale = 1.0 // 最小缩放比例,一般会和初始比例保持一致minimum-scale = 1.0// 最大缩放比例maximum-scale = 1.0// 示例<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />meta除了上述所说的以外还有一些其他辅助功能。 ...

May 28, 2019 · 3 min · jiezi

Script-error-问题解法

问题当第三方脚本报错时因为跨域问题不会暴露详细的错误信息,取而代之的是统一的 Script error 我们遇到的情况是页面和js的二级域名一样,并且设置了document.domain,chrome浏览器能够展示详细错误栈;但是很多手机浏览器依然是Script error。 解法我们使用的cdn是阿里云,实际上他已经给出了此问题的解法 阅读之后我们知道只要给script标签添加crossorigin属性就可以了,之后效果如图 具体方法我们项目使用的是webpack,对html进行修饰的插件大家用的应该都是html-webpack-plugin,此插件的衍生插件script-ext-html-webpack-plugin能够满足我们的需求。 plugins: [ .... new HtmlWebpackPlugin({ inject: true, template: paths.appHtmlProd, }), new ScriptExtHtmlWebpackPlugin({ custom: { test: /\.js$/, attribute: 'crossorigin', value: 'anonymous' } }), ...]

May 24, 2019 · 1 min · jiezi

移动端开发web-app-之移动端布局-知识总结

App 分类 如上图,Native app 是使用原生开发的 app, 优点是性能更好,还能调用系统的 api ,但是发布 app 流程繁琐,而且不跨平台。 而Web app, 优点是跨平台,修改方便,缺点是不能调用原生的 api, 而且用户体验不如原生 app, 好。 而Hybrid app, 结合了上面两个的优点,可以说是很 nice。 尺寸相关概念CSS 像素又称为设备独立像素、逻辑像素。CSS中使用的一个抽象的概念,单位是 px。 值是相对的,并不是绝对的,根据 dpr 来确定一个 CSS 像素代表几个物理像素,还有一些情况,例如用户缩放的时候,,dpr 也会跟着变为 2, 此时一个 CSS 像素代表两个物理像素。 注意: 电脑当中的一个设备像素一般是等于一个 CSS 像素。所以我们在 PS 当中的切图大小,一般也代表物理像素表示的大小。 设备像素又称为物理像素,任何设备屏幕的物理像素的数量自出场开始就是固定不变的,单位是 pt(点)。一个物理像素即屏幕上一个发光的点。物理像素单个点的大小由厂商决定,大小不固定。 屏幕尺寸指的是屏幕对角线的长度,单位为英寸,注意英寸是长度单位,不是面积单位。1英寸(inch)=2.54厘米(cm)。 屏幕尺寸=屏幕斜边的像素/PPI。 像素密度 PPI单位面积上(英寸)像素(设备像素)的数量。它是一个定值,是一个固定的参数。PPI=屏幕斜边的像素/屏幕尺寸。如下: 所以要提高 PPI的话,需要增加水平和竖直方向的像素点数量。因此,同一尺寸(inch)下,PPI提高了一倍,那像素会变为原来的4倍,也就是所 PPI 增加 N 倍,单位面积上的像素点的数量变为原来的 N^N 倍,如下图。查询不同设备的 ppi PPI 的值越高,代表在一定尺寸的屏幕上像素数量越多,屏幕越清晰。如下同一张图片。 但同时,由于单位面积(英寸)并没有变,由于 PPI 的变大,对应的物理像素点会缩小。和上面的物理像素点的规律相反,PPI 增加 N 倍,单位面积上的像素点的大小将变为原来的 1 / N^N 倍。如下图: ...

May 21, 2019 · 1 min · jiezi

关于vue项目中移动端实现用户选择照片照片裁剪一次上传多张图片仿微信发朋友圈功能

关于vue项目中移动端实现用户选择照片、照片裁剪、一次上传多张图片功能(仿微信发朋友圈)。最终要实现的效果如下图所示: 涉及的功能有1、图片的选择2、图片从手机相机选择、拍照3、图片选择后的裁剪4、图片在页面的显示效果5、图片的删除6、base64图片转化为file类型的文件7、图片的上传 首先安装cropperjs 和exif-js 裁剪依赖这两个包cnpm install --save cropperjs exif-js 图片的上传相关代码 图片从手机相机选择、拍照图片选择后的裁剪 initilize(opt) { let self = this; this.options = opt; //创建dom this.createElement(); this.resultObj = opt.resultObj; //初始化裁剪对象 this.cropper = new Cropper(this.preview, { aspectRatio: opt.aspectWithRatio / opt.aspectHeightRatio, autoCropArea: opt.autoCropArea || 0.8, viewMode: 2, guides: true, cropBoxResizable: true, //是否通过拖动来调整剪裁框的大小 cropBoxMovable: true, //是否通过拖拽来移动剪裁框。 dragCrop: false, dragMode: "move", //‘crop’: 可以产生一个新的裁剪框3 ‘move’: 只可以移动3 ‘none’: 什么也不处理 center: true, zoomable: true, //是否允许放大图像。 zoomOnTouch: true, //是否可以通过拖动触摸来放大图像。 scalable: true, // minCropBoxHeight: 750, // minCropBoxWidth: 750, background: false, checkOrientation: true, checkCrossOrigin: true, zoomable: false, zoomOnWheel: false, center: false, toggleDragModeOnDblclick: false, ready: function() { if (opt.aspectRatio == "Free") { let cropBox = self.cropper.cropBox; cropBox.querySelector("span.cropper-view-box").style.outline = "none"; self.cropper.disable(); } } }); }, //创建一些必要的DOM,用于图片裁剪 createElement() { //初始化图片为空对象 this.preview = null; let str = '<div><img id="clip_image" src="originUrl"></div><button type="button" id="cancel_clip">取消</button><button type="button" id="clip_button">确定</button>'; str += '<div class="crop_loading"><div class="crop_content"><div class="crop_text">图片修剪中...</div></div></div>'; str += '<div class="crop_success"><div class="crop_success_text">上传成功</div></div></div>'; let body = document.getElementsByTagName("body")[0]; this.reagion = document.createElement("div"); this.reagion.id = "clip_container"; this.reagion.className = "container"; this.reagion.innerHTML = str; body.appendChild(this.reagion); this.preview = document.getElementById("clip_image"); //绑定一些方法 this.initFunction(); }, //初始化一些函数绑定 initFunction() { let self = this; this.clickBtn = document.getElementById("clip_button"); this.cancelBtn = document.getElementById("cancel_clip"); //确定事件 this.addEvent(this.clickBtn, "click", function() { self.crop(); }); //取消事件 this.addEvent(this.cancelBtn, "click", function() { self.destoried(); }); //清空input的值 this.addEvent(this.fileObj, "click", function() { this.value = ""; }); }, //外部接口,用于input['file']对象change时的调用 clip(e, opt) { let self = this; this.fileObj = e.srcElement; let files = e.target.files || e.dataTransfer.files; if (!files.length) return false; //不是图片直接返回 //调用初始化方法 this.initilize(opt); //获取图片文件资源 this.picValue = files[0]; //调用方法转成url格式 this.originUrl = this.getObjectURL(this.picValue); //每次替换图片要重新得到新的url if (this.cropper) { this.cropper.replace(this.originUrl); } }, //图片转码方法 getObjectURL(file) { let url = null; if (window.createObjectURL != undefined) { // basic url = window.createObjectURL(file); } else if (window.URL != undefined) { // mozilla(firefox) url = window.URL.createObjectURL(file); } else if (window.webkitURL != undefined) { // webkit or chrome url = window.webkitURL.createObjectURL(file); } return url; }, //点击确定进行裁剪 crop() { let self = this; let image = new Image(); let croppedCanvas; let roundedCanvas; // Crop document.querySelector(".crop_loading").style.display = "block"; setTimeout(function() { croppedCanvas = self.cropper.getCroppedCanvas(); // Round roundedCanvas = self.getRoundedCanvas(croppedCanvas); let imgData = roundedCanvas.toDataURL(); image.src = imgData; //判断图片是否大于100k,不大于直接上传,反之压缩 if (imgData.length < 100 * 1024) { // self.resultObj.src = imgData; //图片上传 self.postImg(imgData); } else { image.onload = function() { //压缩处理 let data = self.compress(image, self.Orientation); // self.resultObj.src = data; //图片上传 self.postImg(data); }; } }, 20); }, //获取裁剪图片资源 getRoundedCanvas(sourceCanvas) { let canvas = document.createElement("canvas"); let context = canvas.getContext("2d"); let width = sourceCanvas.width; let height = sourceCanvas.height; canvas.width = width; canvas.height = height; context.imageSmoothingEnabled = true; context.drawImage(sourceCanvas, 0, 0, width, height); context.globalCompositeOperation = "destination-in"; context.beginPath(); context.rect(0, 0, width, height); context.fill(); return canvas; }, //销毁原来的对象 destoried() { let self = this; //移除事件 this.removeEvent(this.clickBtn, "click", null); this.removeEvent(this.cancelBtn, "click", null); this.removeEvent(this.fileObj, "click", null); //移除裁剪框 this.reagion.parentNode.removeChild(this.reagion); //销毁裁剪对象 this.cropper.destroy(); this.cropper = null; }, //图片上传 postImg(imageData) { console.log(imageData); this.$emit("callback", imageData); //这边写图片的上传 let self = this; self.destoried(); this.imgList.push(imageData); }, //图片旋转 rotateImg(img, direction, canvas) { //最小与最大旋转方向,图片旋转4次后回到原方向 const min_step = 0; const max_step = 3; if (img == null) return; //img的高度和宽度不能在img元素隐藏后获取,否则会出错 let height = img.height; let width = img.width; let step = 2; if (step == null) { step = min_step; } if (direction == "right") { step++; //旋转到原位置,即超过最大值 step > max_step && (step = min_step); } else { step--; step < min_step && (step = max_step); } //旋转角度以弧度值为参数 let degree = (step * 90 * Math.PI) / 180; let ctx = canvas.getContext("2d"); switch (step) { case 0: canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0); break; case 1: canvas.width = height; canvas.height = width; ctx.rotate(degree); ctx.drawImage(img, 0, -height); break; case 2: canvas.width = width; canvas.height = height; ctx.rotate(degree); ctx.drawImage(img, -width, -height); break; case 3: canvas.width = height; canvas.height = width; ctx.rotate(degree); ctx.drawImage(img, -width, 0); break; } }, //图片压缩 compress(img, Orientation) { let canvas = document.createElement("canvas"); let ctx = canvas.getContext("2d"); //瓦片canvas let tCanvas = document.createElement("canvas"); let tctx = tCanvas.getContext("2d"); let initSize = img.src.length; let width = img.width; let height = img.height; //如果图片大于四百万像素,计算压缩比并将大小压至400万以下 let ratio; if ((ratio = (width * height) / 4000000) > 1) { console.log("大于400万像素"); ratio = Math.sqrt(ratio); width /= ratio; height /= ratio; } else { ratio = 1; } canvas.width = width; canvas.height = height; // 铺底色 ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, canvas.width, canvas.height); //如果图片像素大于100万则使用瓦片绘制 let count; if ((count = (width * height) / 1000000) > 1) { count = ~~(Math.sqrt(count) + 1); //计算要分成多少块瓦片 // 计算每块瓦片的宽和高 let nw = ~~(width / count); let nh = ~~(height / count); tCanvas.width = nw; tCanvas.height = nh; for (let i = 0; i < count; i++) { for (let j = 0; j < count; j++) { tctx.drawImage( img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh ); ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh); } } } else { ctx.drawImage(img, 0, 0, width, height); } //修复ios上传图片的时候 被旋转的问题 if (Orientation != "" && Orientation != 1) { switch (Orientation) { case 6: //需要顺时针(向左)90度旋转 this.rotateImg(img, "left", canvas); break; case 8: //需要逆时针(向右)90度旋转 this.rotateImg(img, "right", canvas); break; case 3: //需要180度旋转 this.rotateImg(img, "right", canvas); //转两次 this.rotateImg(img, "right", canvas); break; } } //进行最小压缩 let ndata = canvas.toDataURL("image/png", 0.1); console.log("压缩前:" + initSize); console.log("压缩后:" + ndata.length); console.log( "压缩率:" + ~~((100 * (initSize - ndata.length)) / initSize) + "%" ); tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0; return ndata; }, //添加事件 addEvent(obj, type, fn) { if (obj.addEventListener) { obj.addEventListener(type, fn, false); } else { obj.attachEvent("on" + type, fn); } }, //移除事件 removeEvent(obj, type, fn) { if (obj.removeEventListener) { obj.removeEventListener(type, fn, false); } else { obj.detachEvent("on" + type, fn); } }图片在页面的显示效果暂无图片的删除base64图片转化为file类型的文件 bytes = window.atob(this.imgList[i].split(',')[1]); ...

April 25, 2019 · 5 min · jiezi

这款神秘的移动端OCR引擎,如何做到“所见即所得”?

阿里妹导读:随着深度学习,尤其是CNN和RNN等技术的飞速发展,文字识别技术(OCR)近几年得到了迅速的提升。与此同时,在智能化终端的大趋势下,本地化智能识别凭借更高效快捷的体验以及高度的隐私保护和零流量消耗等优势备受瞩目和亲睐,越来越多的应用算法开始倾向终端化完成,OCR也不例外。接下来,蚂蚁金服的算法专家亦弦为我们剖析这个轻量而精准的移动端OCR引擎——xNN-OCR。背景及概述移动端OCR的优势受算法效率和算法模型大小的限制和约束,目前大部分的OCR端上应用都是上传图片到服务端识别再将识别结果回传到客户端。虽然满足了部分业务需求,但一方面,对一些实效性要求较高的业务场景来说用户体验无疑是一个巨大的损失,尤其是弱网环境下。另一方面,在面临大促业务并发请求量过大的情况下,服务端不得不采用降级方案,而如果端上也具备识别能力的话,便可以极大地减少服务端的压力。此外,涉及到身份证、银行卡等重要私人证件采用OCR进行信息提取的时候,端上“识完即焚”这种方式,对这种敏感数据和隐私保护来说是天然的堡垒。因此,具备终端OCR识别能力有着极其重要的业务价值和意义。移动端OCR的难点OCR采用深度学习技术使得识别精度在特定的场景下面有了一定的保障,但模型大小和速度问题在端上依然是一大难题。目前大部分的后台OCR模型通常几十M或者上百M,可能比整个App安装包都要大,是不可能直接放到移动端的,而如果走实时下载的办法,模型过大也会造成下载失败率高、等待时间长、App占用空间大、流量消耗大等问题。另外,现在很多OCR算法在云端GPU上运行尚且需要几十到上百毫秒,要在手机CPU上保持较高的运行效率是一个极大的挑战。我们做了什么?——xNN-OCRxNN-OCR是专门针对移动端本地识别研发的的高精度、高效率、轻体量文字识别引擎,目前支持场景数字、场景英文、场景汉字以及特殊符号的识别。xNN-OCR针对移动端开发和优化了一套基于深度学习的文字检测和文字行识别算法框架,结合xNN的网络压缩和加速能力,检测和识别模型可以压缩到数百K级别,在中端及以上手机CPU上达到实时(最高15FPS),可结合“扫一扫”的模式在视频流中做到所见即所得。移动端OCR识别技术移动端OCR技术主要分为二个方面,一是OCR算法框架的研究和优化,主要目标是探索高精度且轻量级的检测和识别框架,确保在压缩之前模型的大小和速度在一个适当的范围以内,二是利用xNN对模型进行剪枝和量化压缩到实际应用需要的大小。下图是我们以银行卡检测和识别模型为例子展示整个压缩流程精度和模型的变化,其他OCR场景识别均是类似流程。轻量级OCR算法框架的探索目前大部分的移动端OCR技术都是以传统算法为主,在复杂自然场景下识别率相对较低,而基于深度学习的方案可以很好的解决这一类问题,识别率和稳定性远超传统算法。目前主流的深度学习OCR主要分为文字行检测和行识别两大块,下面我们分别介绍下:文字行检测在检测方面,我们将物体检测的Region-CNN框架与FCN的图像分割框架融合在一起,保留了FCN的简单框架以适应端上对模型尺寸和预测时间的要求,同时又在模型中加入了目标检测的位置回归模块,实现了对任意形状文本的检测能力。在基于FCN的整体框架中,为了在精简模型的同时不降低检测效果,我们采用了各种模型精简结构(例如Separable Convolution、Group Convolution + Channel Shuffle等,如下图),模型的尺寸虽然不断减小,精度并未随之下降,在满足端上对模型的苛刻限制的同时取得了较好的检测效果。文字行识别在识别方面,我们在CRNN(CNN+LSTM+CTC)框架基础上进行了优化改进,在Densenet的基础上结合Multiscale Feature、Channel-wise Attention等技术设计出了一套专门用于移动端文字行识别的轻量级CNN网络,同时对LSTM内部参数采用Project技术、全连接层采用SVD、BTD等降维技术进一步减少参数数量(如下图),在ICDAR2013数据集(NOFINETUNE)上,模型大小下降约50%的前提下识别率高出CRNN近4个点,这一改进优化点为上端打下了强有力的基础。xNN模型压缩目前我们的OCR算法模型都是基于tensorflow开发的,xNN已经增加了对TFLite模型的支持,并且在性能上已经远超TFLite。xNN对于我们OCR算法的模型压缩比在10-20倍之间,不同的场景稍微有些区别,与此同时,压缩后模型的精度基本保持不变。由于OCR是一个较复杂的识别任务,算法模型通常都非常大,并且目前大部分的后台OCR算法都是在GPU上运行,要想在端上运行,除了需要在算法层次上做很多优化外,更需要xNN强大的模型压缩和加速能力。移动端OCR应用OCR技术是信息提取和场景理解极其重要的技术手段之一,应用领域非常广泛。目前移动端本地OCR应用从技术角度可以分为2大类,一类是印刷体文字识别,主要是针对字体变化不大、背景单一的场景,例如身份证识别、名片识别、车牌识别等等,另一类是场景类文字识别,主要是针对字体变化大且背景复杂的场景,例如银行卡识别、燃气表/水表识别、门头名识别、场景英文识别(AR翻译)等等,这两类场景中后者识别难度较大,面临的挑战性更多。我们将xNN-OCR用于这些场景并根据场景的特点做了各种优化,取得了一系列的成果,特别是在复杂环境下面识别依然可以保持高效和精准,具体的数据如下表。下面简介了几个比较重要和常见的应用场景。银行卡识别:银行卡识别是金融类行业非常重要的一项技术,是场景数字类识别的一个典型代表。目前大部分银行卡识别均是采用端上识别的方案,因为端上识别不仅能带来更好更快的体验,同时由于不需要数据上传也能一定程度保护用户的隐私数据。基于xNN-OCR开发的银行卡识别在中端手机上耗时<300ms,大部分银行卡都是秒识别。此外,在面对复杂背景以及复杂环境干扰的时候,xNN-OCR在识别速度和精度上均展现了非常明显的优势。燃气表识别:通过OCR识别燃气表读数是目前燃气自助抄表中的一项关键性技术,相比于传统上门抄表,一方面可以节省很大的人力物力,避免上门抄表带来的麻烦,另外一方面也可以减少漏抄、误抄等问题。目前已经有很多燃气公司已经开始应用这一项技术,但实际应用过程中,由于燃气表的位置有时候比较隐蔽,拍摄角度和光照难以控制,通常一般的用户拍照上传到后台识别的图片质量都比较差,识别率偏低。xNN-OCR在端上完成整套识别流程,通过识别反馈引导用户拍摄,可较大程度的提升识别率,在与一家燃气公司的合作中,我们测试识别率可以达到93%+,模型尺寸可保持在500k以内,识别成功耗时<1s。车牌/VIN码识别:车牌/VIN码识别是传统印刷体类文字应用的一个经典场景,在移动警务、车辆维修定损等日常场景中起着非常重要的作用。由于车牌/VIN码识别在实际应用中可能同时需要,为了避免交互流程上的繁琐以及端上2套算法模型过大,xNN-OCR将车牌和VIN码这2个场景识别合二为一,模型尺寸依然<500k,在中端手机上识别成功耗时<1s,并且对光照、模糊、拍摄角度等干扰因素不敏感,同时由于端上可以反复识别寻求置信度最高的结果作为最终结果,所以相对于后台识别“一锤子买卖”而言,在识别精度上会更胜一筹。身份证识别:身份证识别也是金融类行业非常重要的一项技术,在实名认证、安全审核等场景起着非常重要的作用,但由于中文汉字字库较大,导致模型较大,目前大部分的身份证识别均采用的是服务端识别,但由于端侧质量难以控制,往往会导致体验和精度上面难以均衡。xNN-OCR在大字库中文识别方面也作出了一些突破,整体模型小于1M,在端侧用单字识别信度控制识别精度,避免了对图片质量判断的依赖,通过多帧融合提升识别效率,单次识别中端手机上<600ms,识别成功<2s。展望xNN-OCR目前在端上已经能较好的识别场景数字、英文以及部分汉字,无论是模型大小、速度、准确度均已达到工业应用的水平,并且全面超过基于传统算法识别的OCR端上应用,在多个实际应用项目中对比得以验证。另外,我们在端上全量7000多类汉字识别上也做出了一些成果,在不久的将来会分享出来,欢迎有兴趣的同学来一起研究和探讨。我们坚信,随着深度学习的移动端化逐步增强和移动硬件设备的逐步升级,终端智能化的应用与业务将会越来越多,未来xNN-OCR必将会给OCR相关的业务带来更深远的影响和更高的价值。本文作者:亦弦阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。

April 3, 2019 · 1 min · jiezi

前端培训-初级阶段(9 -12)

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。我们要讲什么Iconfont 字体图标(阿里巴巴矢量字体图标库)原理以及实现Media媒体响应式布局Flex弹性盒子布局移动端适配原理 rem(px、em、rem、%、vm)Iconfont 字体图标(阿里巴巴矢量字体图标库)原理以及实现Iconfont是什么?阿里妈妈MUX倾力打造的矢量图标管理、交流平台。设计师将图标上传到Iconfont平台,用户可以自定义下载多种格式的icon,平台也可将图标转换为字体,便于前端工程师自由调整与调用。Iconfont 地址Iconfont 官方帮助中心原理实现1) unicode 引用通过修改 @font-face 定义字体,然后替换元素的 font-family。(顺便说句题外话,有些网页防爬,服务端输出数据1594,前端修改font-fmaily把 1594 对应到 5941,以此来达到看到的数据是对的,爬走的数据是假的。)@font-face{font-family: ‘iconfont’; src: url(‘iconfont.eot’); src: url(‘iconfont.eot?#iefix’) format(’embedded-opentype’), url(‘iconfont.woff’) format(‘woff’), url(‘iconfont.ttf’) format(’truetype’), url(‘iconfont.svg#iconfont’) format(‘svg’);}.iconfont{ font-family:“iconfont” !important; font-size:16px;font-style:normal; -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale;}<i class=“iconfont”>&#x33;</i>unicode是字体在网页端最原始的应用方式,特点是:兼容性最好,支持ie6+,及所有现代浏览器。支持按字体的方式去动态调整图标大小,颜色等等。但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。2) font-class 引用font-class 是 unicode 使用方式的一种变种,主要是解决 unicode 书写不直观,语意不明确的问题。其实和上面相比就是把字符写在 content: “\e502”; 中。@font-face {font-family: “iconfont”; src: url(‘iconfont.eot?t=1552637571329’); /* IE9 / src: url(‘iconfont.eot?t=1552637571329#iefix’) format(’embedded-opentype’), / IE6-IE8 / url(‘data:application/x-font-woff2;charset=utf-8;base64,’) format(‘woff2’), url(‘iconfont.woff?t=1552637571329’) format(‘woff’), url(‘iconfont.ttf?t=1552637571329’) format(’truetype’), / chrome, firefox, opera, Safari, Android, iOS 4.2+ / url(‘iconfont.svg?t=1552637571329#iconfont’) format(‘svg’); / iOS 4.1- /}.iconfont { font-family: “iconfont” !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.icon-h5-copy:before { content: “\e502”;}与unicode使用方式相比,具有如下特点:兼容性良好,支持ie8+,及所有现代浏览器。相比于unicode语意明确,书写更直观。可以很容易分辨这个icon是什么。因为使用class来定义图标,所以当要替换图标时,只需要修改class里面的unicode引用。不过因为本质上还是使用的字体,所以多色图标还是不支持的。3) symbol 引用这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。这种用法其实是做了一个svg的集合,与上面两种相比具有如下特点:支持多色图标了,不再受单色限制。通过一些技巧,支持像字体那样,通过font-size,color来调整样式。兼容性较差,支持 ie9+,及现代浏览器。浏览器渲染svg的性能一般,还不如png。如何使用?选择你所需要的,然后下载。看demo.html,里面有代码。Media媒体响应式布局使用 @media 查询,可以针对媒体类型、屏幕尺寸来设置样式。当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。语法@media mediatype and|not|only (media feature) { CSS-Code;}媒体类型(mediatype)值描述all用于所有设备print用于打印机和打印预览screen用于电脑屏幕,平板电脑,智能手机等speech应用于屏幕阅读器等发声设备媒体特征(media feature)测试页面,常用的其实不多,主要就是判断设备宽度。// https://v4.bootcss.com/docs/4.0/layout/overview/#responsive-breakpoints// Extra small devices (portrait phones, less than 576px)// No media query since this is the default in Bootstrap// Small devices (landscape phones, 576px and up)@media (min-width: 576px) { … }// Medium devices (tablets, 768px and up)@media (min-width: 768px) { … }// Large devices (desktops, 992px and up)@media (min-width: 992px) { … }// Extra large devices (large desktops, 1200px and up)@media (min-width: 1200px) { … }值描述device-height定义输出设备的屏幕可见高度device-width定义输出设备的屏幕可见宽度height定义输出设备中的页面可见区域高度width定义输出设备中的页面可见区域宽度max-device-height定义输出设备的屏幕可见的最大高度max-device-width定义输出设备的屏幕最大可见宽度max-height定义输出设备中的页面最大可见区域高度max-width定义输出设备中的页面最大可见区域宽度min-device-width定义输出设备的屏幕最小可见宽度min-device-height定义输出设备的屏幕的最小可见高度min-height定义输出设备中的页面最小可见区域高度min-width定义输出设备中的页面最小可见区域宽度orientation定义输出设备中的页面可见区域高度是否大于或等于宽度Flex弹性盒子布局Flex布局是什么? 2009年,W3C 提出了一种新的方案—- Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,很快就能很安全的使用。Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。容器的属性(父级)属性名属性值默认值描述displayflex inline-flexstatic定义为弹性盒子flex-directionrow(主轴为水平方向,起点在左端) row-reverse(主轴为水平方向,起点在右端) column(主轴为垂直方向,起点在上沿) column-reverse(主轴为垂直方向,起点在下沿)row排列方向flex-wrapnowrap(不换行) wrap(换行,第一行在上方) wrap-reverse(换行,第一行在下方)nowarp内容放不下,如何换行flex-flow<flex-direction> <flex-wrap>row nowarp是flex-direction属性和flex-wrap属性的简写形式justify-contentflex-start(左对齐) flex-end(右对齐) center(居中) space-between(两端对齐) space-around(每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍);flex-start主轴上的对齐方式,默认来说就是水平对齐text-alignalign-itemsflex-start(顶部) flex-end(底部) center(居中) baseline(文字基线) stretch(垂直拉伸);stretch交叉轴对齐方式,默认来说就是垂直对齐vertical-alignalign-contentflex-start(顶部) flex-end(底部) center(居中) space-between(轴线之间的间隔平均分布) space-around(每根轴线两侧的间隔都相等) stretch(轴线占满整个交叉轴)stretch定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。项目的属性(子集、子元素)order 定义项目的排列顺序。数值越小,排列越靠前,默认为0。(数值是有极限的,demo) flex-grow 属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。负值对该属性无效。flex-basis 定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。flex 属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。align-self 属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。移动端适配原理 rem(px、em、rem、%、vm)移动端适配要解决的问题各终端下的适配问题(宽度 640 750)Retina屏的细节处理 (iOS)如何解决问题rem 基于 html 的 font-size。实时更新 font-size 和屏幕宽度成比例,基于屏幕宽度适配其中又分为基于媒介查询@media screen and (min-width:350px){html{font-size:342%;}}@media screen and (min-width:360px){html{font-size:351.5625%;}}@media screen and (min-width:375px){html{font-size:366.2%;}}@media screen and (min-width:384px){html{font-size:375%;}}@media screen and (min-width:390px){html{font-size:380.859%;}}@media screen and (min-width:410px){html{font-size:400%;}}@media screen and (min-width:432px){ / 魅族3 */html{font-size:421.875%;}}@media screen and (min-width:480px){html{font-size:469%;}}@media screen and (min-width:540px){html{font-size:527.34%;}}@media screen and (min-width:640px){html{font-size: 625%;}}@media screen and (min-width:768px){html{font-size: 750%;}}基于宽度计算(设计稿为 640)*0.15625Flexible 也就是我们常说的手淘H5页面的终端适配方案vw/vh/vmin/vmax 新单位,直接基于屏幕宽度适配vw 代表宽度分为 100 份。vh 代表屏幕高度分为 100 份。vmin 代表宽高最小值分为 100 份。vmax 代表宽高最大值分为 100 份。基于媒介查询做(太费事了)合理的 viewport<!– dpr = 1–> <meta name=“viewport” content=“initial-scale=scale,maximum-scale=scale,minimum-scale=scale,user-scalable=no”> <!– dpr = 2–> <meta name=“viewport” content=“initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no”> <!– dpr = 3–> <meta name=“viewport” content=“initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no”>上面说了那么多,其实还是有坑的,下面我们来说说适配有什么坑。不知道大家做过那种 ppt 的页面没,划来划去动画效果。宽高是没有比例的。所以在一些屏幕特别长的机型上就露底了。一般来说解决方案就是垂直居中页面了手淘方案在新出的曲面屏上表现怪异。那是因为他这个方案有一个最大值,而曲面屏超过了这个极限值。解决方案呢两个vw水平居中页面咯。往期内容前端培训-初级阶段(1 - 4)前端培训-初级阶段(5 - 8)参考资料(引用) 培训目录出处-已备份到笔记Iconfont字体生成原理及使用技巧CSS3 @media 查询 –runoob再聊移动端页面的适配Flex 布局教程:语法篇 –ruanyifengFlex 布局教程:实例篇 –ruanyifeng使用Flexible实现手淘H5页面的终端适配 #17 ...

March 19, 2019 · 2 min · jiezi

移动端 滚动隐藏浏览器地址栏和工具栏

参考:js自动隐藏手机浏览器地址栏文章中实现原理其实很简单,强制页面高度超过手机屏幕高度,手动滚动时会隐藏浏览器自带地址栏和工具栏(qq浏览器不会隐藏工具栏).js实现自动隐藏,window.onload=function(){setTimeout(function() {window.scrollTo(0, 1)}, 0)}原理:js模拟用户滚动,scrollTo.但是我试了各种手机浏览器,页面scrollTop确实变了,都没出来效果,可能我的写法有问题吧。退一步,只能是实现用户滚动隐藏浏览器上下栏了。先说meta<meta name=“apple-mobile-web-app-capable” content=“yes” /><meta name=“apple-mobile-web-app-status-bar-style” content=“black-translucent” /><meta name=“browsermode” content=“application”><meta name=“full-screen” content=“yes” /><meta name=“x5-fullscreen” content=“true” /><meta name=“x5-page-mode” content=“app” /><meta name=“360-fullscreen” content=“true” />除了在uc下可以始终隐藏外,其他浏览器只要路由跳转隐藏就会失败。js方案 1.放开页面高度适配,让页面内容少的情况高度也超出屏幕高度。 问题:手机浏览器自带滚动效果很差,如果页面内容很多的时候,滚动不流畅。 试过overflow-scrolling: touch;效果不理想 2.better-scroll/vue-scroll插件封装列表滚动。 问题:如果整个页面都是列表,会发现用户要滚动页面没有下手的地方。 解决:监听页面路由事件beforeRouteEnter,先禁止掉scroll插件的滚动。 让用户可以滚动页面。 window.onscroll事件中拿到想要的scrollTop后(浏览器已经隐藏地址栏和工具栏),放开scroll插件滚动。

March 18, 2019 · 1 min · jiezi

移动端下拉刷新头实现原理及代码实现

下拉刷新实现原理实现下拉刷新主要分为三步:监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于某个临界值时,显示下拉刷新头,并将页面的overflow属性,设置为false;监听原生touchend事件,若此时元素滑动已经最大值,则进行刷新操作,操作结束后,将页面的overflow属性,设置为auto。代码实现HTML代码<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1”> <title>下拉刷新</title> <style type=“text/css”> html,body, header,p,main,span,ul,li { margin: 0; padding: 0; } #refreshContainer li { background-color: rgba(5, 143, 62, 0.603); margin-bottom: 1px; padding: 30px 10px; } .refreshText { position: absolute; width: 100%; line-height: 80px; text-align: center; left: 0; top: 0; transform: translateY(-80px); } </style></head><body> <div class=“parent”> <p class=“refreshText”></p> <ul id=“refreshContainer”> <li>000</li> <li>111</li> <li>222</li> <li>333</li> <li>444</li> <li>555</li> <li>666</li> <li>777</li> <li>888</li> <li>999</li> </ul> </div></body> </html> JS代码实现<script type=“text/javascript”> window.onload = function () { let container = document.querySelector(’#refreshContainer’); let refreshText = document.querySelector(’.refreshText’); let parent = document.querySelector(’.parent’); let startY = 0; //手指触摸最开始的Y坐标 let endY = 0; //手指结束触摸时的Y坐标 let flag = false; //下拉刷新是否达到了临界值 parent.addEventListener(’touchstart’, function (e) { startY = e.touches[0].pageY; }); parent.addEventListener(’touchmove’, function (e) { if (isTop() && (e.touches[0].pageY - startY) > 50) { flag = true; document.body.style.overflow=‘hidden’; refreshText.style.height = “80px”; parent.style.transform = “translateY(80px)”; parent.style.transition = “all ease 0.5s”; refreshText.innerHTML = “释放立即刷新…”; } }); //松开手指 parent.addEventListener(’touchend’, function (e) { if (isTop() && flag) { refreshText.innerHTML = “正在刷新…”; //进行更新操作,更新结束后,结束下拉刷新 setTimeout(function () { parent.style.transform = “translateY(0)”; document.body.style.overflow = ‘auto’; }, 2000); } }); //scrollTop没有滚过 function isTop() { let t = document.documentElement.scrollTop || document.body.scrollTop; return t === 0 ? true : false; } }</script>注意 body 的 overflow 属性设置。 ...

March 8, 2019 · 1 min · jiezi

使用vue开发移动端管理后台

独立完成一个移动端项目(不是很明白为何会有这样的商品管理后台),还是有些经验不足,包括对产品的全局思考,对插件的选择等,都有考虑不周的缺点,导致自己中途想换图形界面插件,浪费了点时间,这里记录下,总结下经验,理一下思路。1.对于项目的一些心得与体会首先的一点,就是,对于图形界面框架的选型,这个很重要,对于一项目来说,开始动手前就要对项目的设计图有个完整的了解,以便于自己选择插件或者框架;然后就是,对于交互性操作,比如:上传图片,预览图片啥的,应该选择是否是用图形界面框架来实现还是另选专门的插件来实现在完成项目中,我又新学到了上传图片插件vue-core-image-upload,移动端富文本编辑器vue-quill-editor还有个地址的三级联动mt-picker,(是基于mint-ui图形界面框架的)2.rem与px的转换从同事传授中获到的经验,对于rem与px的转换,就是在index.html模板文件中加入下面的脚本,然后就是1rem=100px(这个可能不准确,有更好的方法,各位大佬请在评论中留下,感激不尽)<script type=“text/javascript”> document.getElementsByTagName(“html”)[0].style.fontSize = 100 / 750 * window.screen.width + “px”;</script>3.对于上传图片插件vue-core-image-upload中遇到的坑对于跨域问题,有好多方法可以解决,这里讲的挺多的前端跨域解决方法还有就是后台设置响应头access-control-allow-origin可以指定特定的域名,我这里的后台设置的就是access-control-allow-origin:,就是因为这样,用这个上传图片的插件就会报错Access to XMLHttpRequest at ‘https://….’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.这个问题我蒙圈了好久,和后台也讲了,就是处于蒙圈状态,已经允许跨域了,怎么还报错呢?很烦然后,终于找了个方法解决(有用过其他的上传插件,感觉不好用,代码或者思路好乱)其实这个插件中的文档也有提示,只是刚用,还不是很会就是在使用这个插件的代码中加上这个字段就可以了<vue-core-image-upload class=“btn btn-primary” :crop=“false” input-of-file=“file” @imageuploaded=“loadMainImg” :max-file-size=“5242880” :url=“serverUrl” :credentials=“false” //允许携带cookie></vue-core-image-upload>对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“”。这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“”,请求将会失败。也就是说Access-Control-Allow-Credentials设置为true的情况下Access-Control-Allow-Origin不能设置为*4.基于mint-ui的三级地址选择效果图template文件<div class=“modal” @click=“handleCloseAddress”> <div class=“cateContainer” @click.stop> <div class=“operateBtn”> <div class=“cancelBtn” @click=“handleCloseAddress”>取消</div> <div class=“confirmBtn” @click=“handleCloseAddress”>确定</div> </div> <mt-picker class=“addressPicker” :slots=“myAddressSlots” @change=“onAddressChange”></mt-picker> </div></div>js文件// 定义一个包含中国省市区信息的json文件import addressJson from ‘@/assets/common/address’export default { data() { return { myAddressSlots: [ { flex: 1, values: Object.keys(addressJson), className: ‘slot1’, textAlign: ‘center’ }, { divider: true, content: ‘-’, className: ‘slot2’ }, { flex: 1, values: [‘市辖区’], className: ‘slot3’, textAlign: ‘center’ }, { divider: true, content: ‘-’, className: ‘slot4’ }, { flex: 1, values: [‘东城区’], className: ‘slot5’, textAlign: ‘center’ } ], province:‘省’, city:‘市’, county:‘区/县’, } }, methods: { onAddressChange(picker, values) { if(addressJson[values[0]]) { picker.setSlotValues(1, Object.keys(addressJson[values[0]])); picker.setSlotValues(2, addressJson[values[0]][values[1]]); this.province = values[0]; this.city = values[1]; this.county = values[2]; } }, }}5.关于对是否登录的处理开始也有做过登录的管理后台,不过,在进行登录时,总会一闪过登录的界面,这种感觉很不好,在这里记录下相比之前更好点的方法在main.js文件中添加对router的钩子函数router.beforeEach((to, from, next) => { let token = localStorage.getItem(’token’); if (!token && to.path !== ‘/login’) { next(’/login’); } else { next(); }});通过判断缓存里是否有token来进行路由的跳转相对于之前的那种方法,这里对路由的跳转进行的拦截,在路由进行跳转前,进行判断6.上拉加载mescroll.js插件这里对于分页加载第二页使用的上拉加载的插件还是用了原来的插件,还是感觉挺好用的这里有讲述上拉加载,下拉刷新,滚动无限加载7.移动端富文本插件Vue-Quill-Editor效果图这里有相关案例代码vue-quill-editor<template> <quill-editor v-model=“richTextContent” ref=“myQuillEditor” :options=“editorOption” @change=“onEditorChange($event)"> </quill-editor></template><script> import { quillEditor } from “vue-quill-editor”; import ‘quill/dist/quill.core.css’; import ‘quill/dist/quill.snow.css’; import ‘quill/dist/quill.bubble.css’; export default{ data() { return {} }, methods: { onEditorChange(e) {} } }</script>响应事件onEditorChange(e){ console.log(e) this.richTextContent = e.html;},8.移动端图片预览插件vue-picture-preview<img :src=“url” v-preview=“url” preview-nav-enable=“false” />需要在app.vue中加入如下代码<lg-preview></lg-preview>效果图代码挺少的9.总结在以后的项目中,首先的一件事就是要对产品要有完成的了解,然后进行技术、框架的选型对于插件,自己多尝试才能知道是否符合你的要求正在努力学习中,若对你的学习有帮助,留下你的印记呗(点个赞咯^_^)往期好文推荐:判断ios和Android及PC端webpack打包(有面试题)纯css实现瀑布流(multi-column多列及flex布局)实现单行及多行文字超出后加省略号 ...

March 7, 2019 · 2 min · jiezi

移动端之目录

移动端touch与click

January 10, 2019 · 1 min · jiezi

不要再问我移动适配的问题了

“不要再问我XX的问题”系列:一、不要再问我this的指向问题了二、不要再问我跨域的问题了移动端适配的问题,一般来说我们都不会去深究,因为这种东西都是配置一次就再也不用管的了,接到设计图就按照祖传套路撸就完事了。按部就班的必定只能成为活动页写手,研究透彻以后,才能成为一名专业的活动页写手嘛。纠缠不清的关系文章开始,我们需要来捋清楚像素、视口以及缩放之间种种藕断丝连的关系,来抽丝剥茧一波。像素像素我们写得多了,不就是px嘛,为什么要拿出来说呢?因为像素还不仅仅就是px。设备像素设备像素也可以叫物理像素,由设备的屏幕决定,其实就是屏幕中控制显示的最小单位。设备独立像素设备独立像素是一种可以被程序所控制的虚拟像素,在Web开发中对应CSS像素。DPR设备像素与设备独立像素之间的关系就是,DPR(设备像素比),设备像素比 = 设备像素 / 设备独立像素。这条公式成立的前提是,缩放比为1,原因下面讲到缩放的时候就会知道。根据这种关系,如果设备像素大于设备独立像素(DPR大于1的设备,我们常说的高清屏或者Retina屏),就会出现一个设备独立像素对应多个设备像素的情况:视口遥想从前智能手机刚出的时候,很少网站去特意适配移动端,然而用户是可以直接从手机去访问PC端网站的,所以怎样显示好一个网站,无论这个网站是一个PC网站还是移动端网站,就是亟需解决的问题。所以移动端三个视口布局视口、视觉视口、理想视口横空出世,成为各种移动适配方案的基础。布局视口布局视口是在html元素之上的容器,我们的页面就“装”在布局视口中。想想我们常写的width:100%,这个100%是基于什么计算出来的呢?去翻资料会看到:如果某些属性被赋予一个百分值的话,它的计算值是由这个元素的包含块计算而来的。那html元素的包含块是什么呢?没错,就是我们的布局视口,它是所有CSS百分比推算的根源,如果说CSS是一支画笔,那么布局视口就是那张画布吧。这张画布有一个默认尺寸(如果没有手动去设置meta viewport),一般在768px ~ 1024px间,可以通过document.documentElement.clientWidth获取。这样一来,网页的布局就不再受限于设备的尺寸,即使是小屏幕的移动端设备中也能容得下PC网站。视觉视口视觉视口是指用户通过设备屏幕看到的区域,可以通过缩放来改变视觉视口的大小,并通过window.innerWidth获取。这里有必要讲一下缩放,缩放改变的是CSS像素的大小,放大时CSS像素增大,则一个CSS像素可以跨越更多的设备像素,视觉视口会变小。什么?放大反而视觉视口变小?没错,这是因为视觉视口也是通过CSS像素度量,而放大就是使CSS像素放大,假设屏幕上本来需要200个CSS像素才能占满屏幕,由于放大,现在只需要100个CSS像素就能占满,所以视觉视口的宽就变成100px。虽然缩放改变了CSS像素的大小,但移动端的缩放是不会改变布局视口的,所以缩放并不会影响布局,不过在PC端是会影响布局的。最直观的感受是,我们平时在移动端双指缩放网页,整个网页的布局是没有变化的,可以通过拖动来看到不同区域的东西,但是在PC端进行缩放,比如阅读时想文字大一些而对网页进行放大操作,这时字是放大了,但整个页面的布局会有所改变。那么既然与布局视口无关那还跟谁有关系呢?答案就是下面准备要讲的理想视口,它们之间的计算方式是:缩放系数 = 理想视口宽度 / 视觉视口宽度 理想视口理想视口是指网站在移动设备中的理想大小,这个大小就是设备的屏幕大小。为什么需要理想视口呢?首先,先来看看现在的情况是怎么的不理想。我们在浏览一个没经过移动适配的网站时,由于布局视口在768px ~ 1024px之间,整个网站就“画”在一个这么大的“画布”上,但由于手机屏幕比“画布”小,所以需要经过缩小才能塞进手机屏幕,结果我们浏览网站的时候虽然看得见全貌,但里面的东西都变得很小,需要放大一下才能看得清,就是这么不理想。如果不需要放大就可以看得清那就很理想了嘛。回想一下上面不理想的解决方案,就是将一个大画布经过缩小装进小屏幕里,假设现在画布跟屏幕一样大,就在这个画布上作画,岂不是很合适?所以总结起来,理想视口说白了就是理想的布局视口,通过<meta name=“viewport” content=“width=device-width, initial-scale=1”>来设置。将它们连在一起认识Meta viewport<meta> 元素可提供有关页面的元信息,不会显示在页面上,可以用来告诉浏览器怎样解析页面。<meta>可以设置的东西很多,但这里只讲vieport,它是所有移动适配方案的基础。首先meta viewport的设置格式是<meta name=“viewport” content=“name=value,name=value”,其中name的值可设为:width:将布局视口设置为固定的值,比如375px或者device-width(设备宽度)initial-scale:设置页面的初始缩放minimum-scale:设置最小的缩小程度maximum-scale:设置最大的放大程度user-scalable:设置为no时禁用缩放虽然只有五个值,但仍有一些值得注意的点:设置initial-scale的影响根据公式缩放系数 = 理想视口宽度 / 视觉视口宽度 ,如果设置了initial-scale比如为0.5,那么以iPhone6为例,iPhone6的设备宽度是375px,即理想视口宽度也为375px,所以视觉视口宽度 = 0.5(缩放系数) * 375px(理想视口宽度)。很明显设置了initial-scale就相当于初始化了视觉视口,而且会将布局视口初始化为这个视觉视口的值。width和initial-scale共存上面说到设置了initial-scale相当于初始化了视觉视口和布局视口,但width用于指定布局视口的大小,那么一起设置的话听谁的呢?还是以iPhone6为例,它的尺寸是667(h) * 375(w),如果设置<meta name=“viewport” content=“width=400, initial-scale=1”>,执行一下console.log(布局视口: ${document.documentElement.clientWidth}; 视觉视口: ${window.innerWidth})会得到“布局视口: 400; 视觉视口: 400”。这时候旋转一下设备,这时尺寸变成了667(w) * 375(h),再执行一下console.log(布局视口: ${document.documentElement.clientWidth}; 视觉视口: ${window.innerWidth})会得到“布局视口: 667; 视觉视口: 667”。结论是:width与initial-scale都会初始化布局视口,但浏览器会取其最大值。设置理想视口这时候再看回<meta name=“viewport” content=“width=device-width, initial-scale=1”>,明明width=device-width和initial-scale=1都是去初始化布局视口成理想的布局视口,只写其中一个不就完了嘛,为什么要两个都一起写呢?因为有的浏览器只设置其中一个,不能保证理想视口的尺寸能随着屏幕的旋转而正确改变,所以两个一起写只是为了解决兼容性问题。舒服地还原移动端设计图上面说了很多理论知识,其实就是为了能有一套方案舒服地还原移动端设计图,做出一个专为移动端访问的页面。经典的问题图片这里的图片问题是指高清/Retina屏下图片会显示得比较模糊,这是因为我们平时使用的图片大多数是png、jpg这样格式的图片,它们称作是位图图像(bitmap),是由一个个像素点构成,缩放会失真。上面讲像素的时候说过,这种高清/Retina屏DPR大于一,则一像素横跨了多个设备像素,而位图图像需要一个像素点对应一个设备像素才清晰。所以假设一张100 x 100的图片放在普通屏上看是清晰的,放到高清/Retina屏上就会显得比较模糊,那是因为本来100 x 100的图片在普通屏上图片像素与设备像素一一对应,而到了高清/Retina屏上一个图片像素却要对应多个设备像素,这样一来看起来图片就比较模糊。如图所示,如果一个图片像素要对应多个设备像素的话,那这些设备像素只能显示成跟这个图片像素差不多的颜色,导致看起来会模糊。既然知道了问题产生的原因,那解决方法也很简单,位图图像需要一个像素点对应一个设备像素才清晰嘛,那就本来是100 x 100的图片在DPR为1的屏幕上显示清晰,在DPR为2的屏幕上显示模糊,那就在DPR为2的屏幕上放200 x 200的图好了,这样就一一对应了。1px边框“你看看设计图这根线是很细的,为什么你实现出来那么粗,看起来很劣质的感觉。”没道理呀,设计图量的是1px,css写的也是1px,怎么会粗了呢?一般设计师出图的时候,都会按照一个尺寸作为标准来出图,比如按照iPhone6的尺寸出图,就是一张750px宽的设计图,这个750px其实就是iPhone6的设备像素,在测量设计图时量到的1px其实是1设备像素,而当我们设置<meta name=“viewport” content=“width=device-width, initial-scale=1”>时,布局视口等于理想视口等于375px,并且由于iPhone6的DPR为2,写css时的1px对应的是2设备像素,所以看起来会粗一点。那么只要写0.5px就是对应1设备像素了嘛。是的,道理是这么说,但是很多浏览器并不支持0.5px的写法,导致显示不出来,但不要紧,网上很多方法解决这个问题的方法就不细说了,这里只是讲清楚1px边框问题产生的原因。还原设计图因为PC端屏幕一般都会比设计图尺寸要大,所以只需要居中固定一个内容区用于显示设计图的内容,其余多出的地方留白即可。而移动端屏幕有大有小,设计图一般会以一款机型为标准来出图,比如说iPhone6的尺寸,如果不经处理直接量设计图就开干会出现什么问题呢?(从左到右为iPhone4、iPhone6、iPhone plus)可以看到以iPhone6为标准出的设计图测量出来350px x 350px的元素在iPhone6上写width: 350px;height: 350px;是刚刚好的,左右的间隙各有10px,但小一点的屏幕iPhone4横向滚动条都出来了,而plus左右间隙明显比10px大很多,这样一来不同尺寸的屏幕出来的效果跟设计图的效果就会有不同程度的出入,这并不是我们想要的,我们想要的是不同尺寸的屏幕显示的效果与设计图比例是一致的。既然想要的是不同屏幕尺寸显示的比例与设计图一致,那么显然适配方案就是等比缩放。(以下代码都是为了讲述原理,没有过多的细节考虑与测试,不能用于生产环境)viewport方案说到缩放,首先想到的当然是initial-scale。回想一下initial-scale的作用:设置了initial-scale就相当于初始化了视觉视口,而且会将布局视口初始化为这个视觉视口的值。那么我们是不是可以以设计图为基准等比缩放布局视口从而适配呢?<script> const scale = window.screen.width / 750 document.write(&lt;meta name="viewport" content="initial-scale=${scale}"&gt;)</script>这种方式进行适配优点是简单粗暴,缺点是太简单粗暴了,因为viewport的设置是影响全局的,这样一来虽然可以直接将设计图量得的尺寸写到css上,但如果有一些需要地方不需要等比缩放而需要设置固定尺寸,比如要求在不同尺寸屏幕上显示固定大小的文字,或者你引进了一个库,里面的有样式你也不知道人家是按照怎样的适配方案进行适配的,那么到了你的项目里由于全局的viewport缩放,可能会影响到这个库的显示效果。rem方案不同于px是固定尺寸单位,rem是相对单位,相对于html标签字体大小的单位。比如html标签的font-size为100px,那么1rem就等于100px。借助rem这个相对单位我们同样可以达到等比缩放的效果。这个方案不需要对viewport进行缩放,所以首先按照惯例我们让布局视口等于理想视口:<meta name=“viewport” content=“width=device-width, initial-scale=1”>还是以iPhone6的设备像素为标准的设计图,宽是750px,假设以设计图为标准的html标签的font-size为100px,所以1rem = 100px,那么这个设计图总宽就有7.5rem以总宽是7.5rem的设计图为标准,则不同屏幕尺寸的总宽应该也是7.5rem,由于上面设置了布局视口等于理想视口,所以以iPhone6为例,iPhone6的布局视口等于理想视口,则它的布局视口为375px(也就是总宽7.5rem),现在只需要解决在布局视口为375px的情况下,html的font-size需要设置多少。很简单,html font-size * 7.5 = 375,那么font-size为50px。拓展到其他屏幕document.documentElement.style.fontSize = ${document.documentElement.clientWidth / 7.5}px现在我们只需要测量设计图,比如设计图有一个300px的元素,那我们写css的时候就写成3rem(由于以1rem = 100px为基准,所以这里300px / 100即可)使用这个方案,我们只对需要等比缩放的元素使用rem,而要求固定尺寸的地方使用px即可,这样一来相对于viewport方案来说就比较灵活,可以按需使用而不是一刀切。不过这种方案写css的时候可能会没那么直观,成本可能会高一点点,但是借助构建工具或者less/sass可以解决,毕竟现在应该很少项目不使用这些工具的了吧。加强版rem方案这里所说的加强版rem方案其实就是手淘的Flexible方案(也类似移动端高清、多屏适配方案),究竟加强了什么呢?那就是,通过设置viewport进而全局解决1px边框问题。既然要通过设置viewport来解决1px边框问题,那设置这个viewport的方式肯定内有乾坤:if (!dpr && !scale) { var isAndroid = win.navigator.appVersion.match(/android/gi); var isIPhone = win.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = win.devicePixelRatio; if (isIPhone) { if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { dpr = 3; } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ dpr = 2; } else { dpr = 1; } } else { // 其他设备下,仍旧使用1倍的方案 dpr = 1; } scale = 1 / dpr;}得出的scale用于设置viewport的缩放document.write(&lt;meta name="viewport" content="initial-scale=${scale}"&gt;),这样一来,对于Retina屏将viewport缩放为1 / dpr最终产生的效果是,1px css像素严格等于1px 设备像素,由此解决了1px边框问题。那为什么只对iPhone进行缩放呢?请看大漠老师的文章再谈Retina下1px的解决方案。其他与rem相关的配置与上面的rem方案类似,这里就不再展开说了。这个加强版rem方案最大的优势是解决了1px边框问题,但由此也进行了viewport的缩放,仍然会面临着上面说的viewport方案涉及到的一些影响,为此该方案会通过给html设置data-dprdocument.documentElement.setAttribute(‘data-dpr’, dpr)从而写css的时候可以针对不同的dpr固定设置尺寸:.test { width: 1rem; height: 2rem; font-size: 12px; }[data-dpr=“2”] .test { font-size: 13px;}[data-dpr=“3”] .test { font-size: 14px;}vw方案vw也是一个相对单位,它相对的是布局视口,1vw就是1%的布局视口宽度。其实rem方案就是在模拟vw,来看看使用vw怎么做。还是熟悉的iPhone6标准设计图,宽750px。那么1vw = 1%视口宽度的话,按设计图来说就是100vw = 750px,则1vw = 7.5px。设计图量得一个元素是100px,css需要写成 Xvw * 7.5 = 100,所以X就等于13.3vw。计算的话还是交给构建工具即可,详细请看再聊移动端页面的适配rem方案有的优势vw也有,而且也不会像rem那么绕,但就是兼容性不够rem好,长远来看vw最后会接棒rem作为移动适配的主力,因为它生来就干这个事情呢。终于结束了没有银弹。全局viewport缩放方案很粗暴?但对于要求不高也不需要兼顾固定尺寸的页面,上来就全局缩放,拿起设计稿就可以写代码了。要求高又想灵活,还会怕构建的那一点点麻烦吗?rem方案走起。兼容性不需要考虑,那vw方案直白又优雅不试试看吗?方案没有优劣之分只有合适与否。最后,如果有说得不对的地方,还望指正。 ...

January 6, 2019 · 1 min · jiezi

原生 js 实现移动端 Touch 轮播图

Touch 轮播图touch轮播图其实就是通过手指的滑动,来左右切换轮播图,下面我们通过一个案例,来实现下。1. html 结构结构上,还是用ul、li来存放轮播图片,ol、li来存放轮播小圆点:2. 样式初始化html的一些标签,都会有一些默认样式,比如body标签默认是有一个边距的,为了不影响美观,我们需要清除掉。/* 清除标签默认边距 /body,ul,li,ol,img { margin: 0; padding: 0;}/ 清除 ul 等标签前面的“小圆点” /ul,li,ol { list-style-type: none;}/ 图片自适应 /img { width: 100%; height: auto; border: none; / ie8 / display: block; -ms-interpolation-mode: bicubic; /为了照顾ie图片缩放失真/}3. 添加样式在前面讲特效的时候,我们说过如何使用原生js实现移一个轮播图的概念,但是当时的方式是通过li浮动,这里给大家介绍一种新的方——定位。思路:给ul外层的盒子一个相对定位;这里的ul高度不能写死,它应该是li撑开的高度,但是由于li绝对定位,没办法撑开这个高度,所以这里的ul需要在js里面动态设置高度;给li设置相对定位,并且left、top都为0,再给li添加一个transform:translateX(300%)属性,目的是初始化显示的图片为空,然后在js里只需要动态设置每个li的translateX值,即可实现轮播;设置小圆点区域,因为小圆点个数未知,所以ol的宽度也未知,想要让一个未知宽度的盒子水平居中,可以使用absolute定位结合left百分比的方式实现;给ol下面的li设置一个宽高添加圆角边框属性,并且左浮动,这样就能显示一排空心的小圆点了;最后,添加一个样式类,里面设置一个背景属性,用来显示当前展示图片对应的小圆点。/ 轮播图最外层盒子 /.carousel { position: relative; overflow: hidden;}.carousel ul { / 这个高度需要在JS里面动态添加 /}.carousel ul li { position: absolute; width: 100%; left: 0; top: 0; / 使用 transform:translaX(300%) 暂时将 li 移动到屏幕外面去*/ -webkit-transform: translateX(300%); transform: translateX(300%);}/* 小圆点盒子 /.carousel .points { / 未知宽度的盒子,使用 absolute 定位,结合 transform 的方式进行居中 / position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%);}/ 小圆点 /.carousel .points li { width: 5px; height: 5px; border-radius: 50%; border: 1px solid #fff; float: left; margin: 0 2px;}/ 选中小圆点的样式类 */.carousel .points li.active { background-color: #fff;}4. js 准备工作先不考虑别的,js在初始化的时候,首先要做的就是给ul添加上一个高度,不然图片是不显示的。给UL动态设置高度动态生成小圆点 (根据图片的张数创建小圆点个数,i=0 添加active)初始化三个li的基本位置定义三个变量,分别用来存储三个li的下(left存储最后一张图片的下标,center和right分别存储第一张和第二张的下标)通过数组[下标]的方式给三个li设置定位后left方向的位置var carousel = document.querySelector(’.carousel’);var carouselUl = carousel.querySelector(‘ul’);var carouselLis = carouselUl.querySelectorAll(’li’);var points = carousel.querySelector(‘ol’);// 屏幕的宽度(轮播图显示区域的宽度)var screenWidth = document.documentElement.offsetWidth;// 1- ul设置高度carouselUl.style.height = carouselLis[0].offsetHeight + ‘px’;// 2- 生成小圆点for(var i = 0; i < carouselLis.length; i++){ var li = document.createElement(’li’); if(i == 0){ li.classList.add(‘active’); }// points.appendChild(li);}// 3- 初始三个 li 固定的位置var left = carouselLis.length - 1;var center = 0;var right = 1;// 归位carouselLis[left].style.transform = ’translateX(’+ (-screenWidth) +‘px)’;carouselLis[center].style.transform = ’translateX(0px)’;carouselLis[right].style.transform = ’translateX(’+ screenWidth +‘px)’;效果图:5. 添加定时器,让图片动起来轮播图都会自己轮播,所以需要用到定时器,每隔一段时间执行一次轮转函数。添加定时器,定时器里面轮转下标极值判断设置过渡(替补的那张不需要过渡)归位小圆点焦点联动var timer = null;// 调用定时器timer = setInterval(showNext, 2000);// 轮播图片切换function showNext(){ // 轮转下标 left = center; center = right; right++; // 极值判断 if(right > carouselLis.length - 1){ right = 0; } //添加过渡 carouselLis[left].style.transition = ’transform 1s’; carouselLis[center].style.transition = ’transform 1s’; // 右边的图片永远是替补的,不能添加过渡 carouselLis[right].style.transition = ’none’; // 归位 carouselLis[left].style.transform = ’translateX(’+ (-screenWidth) +‘px)’; carouselLis[center].style.transform = ’translateX(0px)’; carouselLis[right].style.transform = ’translateX(’+ screenWidth +‘px)’; // 自动设置小圆点 setPoint();}// 动态设置小圆点的active类var pointsLis = points.querySelectorAll(’li’);function setPoint(){ for(var i = 0; i < pointsLis.length; i++){ pointsLis[i].classList.remove(‘active’); } pointsLis[center].classList.add(‘active’);}效果图:6. touch 滑动移动端的轮播图,配合touch滑动事件,效果更加友好。分别绑定三个touch事件touchstart里面记录手指的位置,清除定时器,记录时间touchmove里面获取差值,同时清除过渡,累加上差值的值touchend里面判断是否滑动成功,滑动的依据是滑动的距离(绝对值)超过屏幕的三分之一或者滑动的时间小于300毫秒同时距离大于30(防止点击就跑)的时候都认为是滑动成功在滑动成功的条件分支里面在判断滑动的方向,根据方向选择调用上一张还是下一张的逻辑在滑动失败的条件分支里面添加上过渡,重新进行归位重启定时器var carousel = document.querySelector(’.carousel’);var carouselUl = carousel.querySelector(‘ul’);var carouselLis = carouselUl.querySelectorAll(’li’);var points = carousel.querySelector(‘ol’);// 屏幕的宽度var screenWidth = document.documentElement.offsetWidth;var timer = null;// 设置 ul 的高度carouselUl.style.height = carouselLis[0].offsetHeight + ‘px’;// 动态生成小圆点for (var i = 0; i < carouselLis.length; i++) { var li = document.createElement(’li’); if (i == 0) { li.classList.add(‘active’); } points.appendChild(li);}// 初始三个固定的位置var left = carouselLis.length - 1;var center = 0;var right = 1;// 归位(多次使用,封装成函数)setTransform();// 调用定时器timer = setInterval(showNext, 2000);// 分别绑定touch事件var startX = 0; // 手指落点var startTime = null; // 开始触摸时间carouselUl.addEventListener(’touchstart’, touchstartHandler); // 滑动开始绑定的函数 touchstartHandlercarouselUl.addEventListener(’touchmove’, touchmoveHandler); // 持续滑动绑定的函数 touchmoveHandlercarouselUl.addEventListener(’touchend’, touchendHandeler); // 滑动结束绑定的函数 touchendHandeler// 轮播图片切换下一张function showNext() { // 轮转下标 left = center; center = right; right++; // 极值判断 if (right > carouselLis.length - 1) { right = 0; } //添加过渡(多次使用,封装成函数) setTransition(1, 1, 0); // 归位 setTransform(); // 自动设置小圆点 setPoint();}// 轮播图片切换上一张function showPrev() { // 轮转下标 right = center; center = left; left–; // 极值判断 if (left < 0) { left = carouselLis.length - 1; } //添加过渡 setTransition(0, 1, 1); // 归位 setTransform(); // 自动设置小圆点 setPoint();}// 滑动开始function touchstartHandler(e) { // 清除定时器 clearInterval(timer); // 记录滑动开始的时间 startTime = Date.now(); // 记录手指最开始的落点 startX = e.changedTouches[0].clientX;}// 滑动持续中function touchmoveHandler(e) { // 获取差值 自带正负 var dx = e.changedTouches[0].clientX - startX; // 干掉过渡 setTransition(0, 0, 0); // 归位 setTransform(dx);}// 滑动结束function touchendHandeler(e) { // 在手指松开的时候,要判断当前是否滑动成功 var dx = e.changedTouches[0].clientX - startX; // 获取时间差 var dTime = Date.now() - startTime; // 滑动成功的依据是滑动的距离(绝对值)超过屏幕的三分之一 或者滑动的时间小于300毫秒同时滑动的距离大于30 if (Math.abs(dx) > screenWidth / 3 || (dTime < 300 && Math.abs(dx) > 30)) { // 滑动成功了 // 判断用户是往哪个方向滑 if (dx > 0) { // 往右滑 看到上一张 showPrev(); } else { // 往左滑 看到下一张 showNext(); } } else { // 添加上过渡 setTransition(1, 1, 1); // 滑动失败了 setTransform(); } // 重新启动定时器 clearInterval(timer); // 调用定时器 timer = setInterval(showNext, 2000);}// 设置过渡function setTransition(a, b, c) { if (a) { carouselLis[left].style.transition = ’transform 1s’; } else { carouselLis[left].style.transition = ’none’; } if (b) { carouselLis[center].style.transition = ’transform 1s’; } else { carouselLis[center].style.transition = ’none’; } if (c) { carouselLis[right].style.transition = ’transform 1s’; } else { carouselLis[right].style.transition = ’none’; }}// 封装归位function setTransform(dx) { dx = dx || 0; carouselLis[left].style.transform = ’translateX(’ + (-screenWidth + dx) + ‘px)’; carouselLis[center].style.transform = ’translateX(’ + dx + ‘px)’; carouselLis[right].style.transform = ’translateX(’ + (screenWidth + dx) + ‘px)’;}// 动态设置小圆点的active类var pointsLis = points.querySelectorAll(’li’);function setPoint() { for (var i = 0; i < pointsLis.length; i++) { pointsLis[i].classList.remove(‘active’); } pointsLis[center].classList.add(‘active’);}效果图: ...

January 3, 2019 · 3 min · jiezi

原生js实现移动端Touch轮播图

Touch 轮播图touch轮播图其实就是通过手指的滑动,来左右切换轮播图,下面我们通过一个案例,来实现下。1. html 结构结构上,还是用ul、li来存放轮播图片,ol、li来存放轮播小圆点:2. 样式初始化html的一些标签,都会有一些默认样式,比如body标签默认是有一个边距的,为了不影响美观,我们需要清除掉。/* 清除标签默认边距 /body,ul,li,ol,img { margin: 0; padding: 0;}/ 清除 ul 等标签前面的“小圆点” /ul,li,ol { list-style-type: none;}/ 图片自适应 /img { width: 100%; height: auto; border: none; / ie8 / display: block; -ms-interpolation-mode: bicubic; /为了照顾ie图片缩放失真/}3. 添加样式在前面讲特效的时候,我们说过如何使用原生js实现移一个轮播图的概念,但是当时的方式是通过li浮动,这里给大家介绍一种新的方——定位。思路:给ul外层的盒子一个相对定位;这里的ul高度不能写死,它应该是li撑开的高度,但是由于li绝对定位,没办法撑开这个高度,所以这里的ul需要在js里面动态设置高度;给li设置相对定位,并且left、top都为0,再给li添加一个transform:translateX(300%)属性,目的是初始化显示的图片为空,然后在js里只需要动态设置每个li的translateX值,即可实现轮播;设置小圆点区域,因为小圆点个数未知,所以ol的宽度也未知,想要让一个未知宽度的盒子水平居中,可以使用absolute定位结合left百分比的方式实现;给ol下面的li设置一个宽高添加圆角边框属性,并且左浮动,这样就能显示一排空心的小圆点了;最后,添加一个样式类,里面设置一个背景属性,用来显示当前展示图片对应的小圆点。/ 轮播图最外层盒子 /.carousel { position: relative; overflow: hidden;}.carousel ul { / 这个高度需要在JS里面动态添加 /}.carousel ul li { position: absolute; width: 100%; left: 0; top: 0; / 使用 transform:translaX(300%) 暂时将 li 移动到屏幕外面去*/ -webkit-transform: translateX(300%); transform: translateX(300%);}/* 小圆点盒子 /.carousel .points { / 未知宽度的盒子,使用 absolute 定位,结合 transform 的方式进行居中 / position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%);}/ 小圆点 /.carousel .points li { width: 5px; height: 5px; border-radius: 50%; border: 1px solid #fff; float: left; margin: 0 2px;}/ 选中小圆点的样式类 */.carousel .points li.active { background-color: #fff;}4. js 准备工作先不考虑别的,js在初始化的时候,首先要做的就是给ul添加上一个高度,不然图片是不显示的。给UL动态设置高度动态生成小圆点 (根据图片的张数创建小圆点个数,i=0 添加active)初始化三个li的基本位置定义三个变量,分别用来存储三个li的下(left存储最后一张图片的下标,center和right分别存储第一张和第二张的下标)通过数组[下标]的方式给三个li设置定位后left方向的位置var carousel = document.querySelector(’.carousel’);var carouselUl = carousel.querySelector(‘ul’);var carouselLis = carouselUl.querySelectorAll(’li’);var points = carousel.querySelector(‘ol’);// 屏幕的宽度(轮播图显示区域的宽度)var screenWidth = document.documentElement.offsetWidth;// 1- ul设置高度carouselUl.style.height = carouselLis[0].offsetHeight + ‘px’;// 2- 生成小圆点for(var i = 0; i < carouselLis.length; i++){ var li = document.createElement(’li’); if(i == 0){ li.classList.add(‘active’); }// points.appendChild(li);}// 3- 初始三个 li 固定的位置var left = carouselLis.length - 1;var center = 0;var right = 1;// 归位carouselLis[left].style.transform = ’translateX(’+ (-screenWidth) +‘px)’;carouselLis[center].style.transform = ’translateX(0px)’;carouselLis[right].style.transform = ’translateX(’+ screenWidth +‘px)’;效果图:5. 添加定时器,让图片动起来轮播图都会自己轮播,所以需要用到定时器,每隔一段时间执行一次轮转函数。添加定时器,定时器里面轮转下标极值判断设置过渡(替补的那张不需要过渡)归位小圆点焦点联动var timer = null;// 调用定时器timer = setInterval(showNext, 2000);// 轮播图片切换function showNext(){ // 轮转下标 left = center; center = right; right++; // 极值判断 if(right > carouselLis.length - 1){ right = 0; } //添加过渡 carouselLis[left].style.transition = ’transform 1s’; carouselLis[center].style.transition = ’transform 1s’; // 右边的图片永远是替补的,不能添加过渡 carouselLis[right].style.transition = ’none’; // 归位 carouselLis[left].style.transform = ’translateX(’+ (-screenWidth) +‘px)’; carouselLis[center].style.transform = ’translateX(0px)’; carouselLis[right].style.transform = ’translateX(’+ screenWidth +‘px)’; // 自动设置小圆点 setPoint();}// 动态设置小圆点的active类var pointsLis = points.querySelectorAll(’li’);function setPoint(){ for(var i = 0; i < pointsLis.length; i++){ pointsLis[i].classList.remove(‘active’); } pointsLis[center].classList.add(‘active’);}效果图:6. touch 滑动移动端的轮播图,配合touch滑动事件,效果更加友好。分别绑定三个touch事件touchstart里面记录手指的位置,清除定时器,记录时间touchmove里面获取差值,同时清除过渡,累加上差值的值touchend里面判断是否滑动成功,滑动的依据是滑动的距离(绝对值)超过屏幕的三分之一或者滑动的时间小于300毫秒同时距离大于30(防止点击就跑)的时候都认为是滑动成功在滑动成功的条件分支里面在判断滑动的方向,根据方向选择调用上一张还是下一张的逻辑在滑动失败的条件分支里面添加上过渡,重新进行归位重启定时器var carousel = document.querySelector(’.carousel’);var carouselUl = carousel.querySelector(‘ul’);var carouselLis = carouselUl.querySelectorAll(’li’);var points = carousel.querySelector(‘ol’);// 屏幕的宽度var screenWidth = document.documentElement.offsetWidth;var timer = null;// 设置 ul 的高度carouselUl.style.height = carouselLis[0].offsetHeight + ‘px’;// 动态生成小圆点for (var i = 0; i < carouselLis.length; i++) { var li = document.createElement(’li’); if (i == 0) { li.classList.add(‘active’); } points.appendChild(li);}// 初始三个固定的位置var left = carouselLis.length - 1;var center = 0;var right = 1;// 归位(多次使用,封装成函数)setTransform();// 调用定时器timer = setInterval(showNext, 2000);// 分别绑定touch事件var startX = 0; // 手指落点var startTime = null; // 开始触摸时间carouselUl.addEventListener(’touchstart’, touchstartHandler); // 滑动开始绑定的函数 touchstartHandlercarouselUl.addEventListener(’touchmove’, touchmoveHandler); // 持续滑动绑定的函数 touchmoveHandlercarouselUl.addEventListener(’touchend’, touchendHandeler); // 滑动结束绑定的函数 touchendHandeler// 轮播图片切换下一张function showNext() { // 轮转下标 left = center; center = right; right++; // 极值判断 if (right > carouselLis.length - 1) { right = 0; } //添加过渡(多次使用,封装成函数) setTransition(1, 1, 0); // 归位 setTransform(); // 自动设置小圆点 setPoint();}// 轮播图片切换上一张function showPrev() { // 轮转下标 right = center; center = left; left–; // 极值判断 if (left < 0) { left = carouselLis.length - 1; } //添加过渡 setTransition(0, 1, 1); // 归位 setTransform(); // 自动设置小圆点 setPoint();}// 滑动开始function touchstartHandler(e) { // 清除定时器 clearInterval(timer); // 记录滑动开始的时间 startTime = Date.now(); // 记录手指最开始的落点 startX = e.changedTouches[0].clientX;}// 滑动持续中function touchmoveHandler(e) { // 获取差值 自带正负 var dx = e.changedTouches[0].clientX - startX; // 干掉过渡 setTransition(0, 0, 0); // 归位 setTransform(dx);}// 滑动结束function touchendHandeler(e) { // 在手指松开的时候,要判断当前是否滑动成功 var dx = e.changedTouches[0].clientX - startX; // 获取时间差 var dTime = Date.now() - startTime; // 滑动成功的依据是滑动的距离(绝对值)超过屏幕的三分之一 或者滑动的时间小于300毫秒同时滑动的距离大于30 if (Math.abs(dx) > screenWidth / 3 || (dTime < 300 && Math.abs(dx) > 30)) { // 滑动成功了 // 判断用户是往哪个方向滑 if (dx > 0) { // 往右滑 看到上一张 showPrev(); } else { // 往左滑 看到下一张 showNext(); } } else { // 添加上过渡 setTransition(1, 1, 1); // 滑动失败了 setTransform(); } // 重新启动定时器 clearInterval(timer); // 调用定时器 timer = setInterval(showNext, 2000);}// 设置过渡function setTransition(a, b, c) { if (a) { carouselLis[left].style.transition = ’transform 1s’; } else { carouselLis[left].style.transition = ’none’; } if (b) { carouselLis[center].style.transition = ’transform 1s’; } else { carouselLis[center].style.transition = ’none’; } if (c) { carouselLis[right].style.transition = ’transform 1s’; } else { carouselLis[right].style.transition = ’none’; }}// 封装归位function setTransform(dx) { dx = dx || 0; carouselLis[left].style.transform = ’translateX(’ + (-screenWidth + dx) + ‘px)’; carouselLis[center].style.transform = ’translateX(’ + dx + ‘px)’; carouselLis[right].style.transform = ’translateX(’ + (screenWidth + dx) + ‘px)’;}// 动态设置小圆点的active类var pointsLis = points.querySelectorAll(’li’);function setPoint() { for (var i = 0; i < pointsLis.length; i++) { pointsLis[i].classList.remove(‘active’); } pointsLis[center].classList.add(‘active’);}效果图: ...

January 2, 2019 · 3 min · jiezi

利用 canvas 压缩图片

利用 canvas 压缩图片前言在一个移动端的项目中,图片上传是一个比较常用的功能。但是,目前手机的随便拍的照片一张都要好几 M , 直接上传的话特别耗费流量,而且所需时间也比较长。所以需要前端在上传之前先对图片进行压缩。原理要使用 js 实现图片压缩效果, 原理其实很简单,主要是:利用 canvas 的 drawImage 将目标图片画到画布上利用画布调整绘制尺寸,以及导出的 quality ,确定压缩的程度利用 canvas的 toDataURL 或者 toBlob 可以将画布上的内容导出成 base64 格式的数据。注意点IOS 下会出现图片翻转的问题这个需要 import EXIF from ’exif-js’;来获取到手机的方向,然后对 canvas 的宽高进行处理压缩到特定大小let imgDataLength = dataUrl.length; 获取到数据后,判断压缩后的图片大小是否满足需求,否则就降低尺寸以及质量,再次压缩quality 对 png 等无效,所以导出格式统一为 jpeg ,透明背景填充为白色// 填充白色背景ctx.fillStyle = fillBgColor;ctx.fillRect(0, 0, size.w, size.h);具体源码/** * 文件读取并通过canvas压缩转成base64 * @param files * @param callback ///EXIF js 可以读取图片的元信息 https://github.com/exif-js/exif-jsimport EXIF from ’exif-js’;// 压缩图片时 质量减少的值const COMPRESS_QUALITY_STEP = 0.03;const COMPRESS_QUALITY_STEP_BIG = 0.06;// 压缩图片时,图片尺寸缩放的比例,eg:0.9, 等比例缩放为0.9const COMPRESS_SIZE_RATE = 0.9;let defaultOptions = { removeBase64Header: true, // 压缩后允许的最大值,默认:300kb maxSize: 200 * 1024, fillBgColor: ‘#ffffff’};/* * 将待上传文件列表压缩并转换base64 * !!!! 注意 : 图片会默认被转为 jpeg , 透明底会加白色背景 * files : 文件列表 ,必须是数组 * callback : 回调,每个文件压缩成功后都会回调, * options :配置 * options.removeBase64Header : 是否需要删除 ‘data:image/jpeg;base64,‘这段前缀,默认true * @return { base64Data: ‘’,fileType: ’’ }, //fileType强制改为jpeg /export function imageListConvert(files, callback, options = {}) { if (!files.length) { console.warn(‘files is null’); return; } options = { …defaultOptions, …options }; // 获取图片方向--iOS拍照下有值 EXIF.getData(files[0], function() { let orientation = EXIF.getTag(this, ‘Orientation’); for (let i = 0, len = files.length; i < len; i++) { let file = files[i]; let fileType = getFileType(file.name); //强制改为jpeg fileType = ‘jpeg’; let reader = new FileReader(); reader.onload = (function() { return function(e) { let image = new Image(); image.onload = function() { let data = convertImage( image, orientation, fileType, options.maxSize, options.fillBgColor ); if (options.removeBase64Header) { data = removeBase64Header(data); } callback({ base64Data: data, fileType: fileType }); }; image.src = e.target.result; }; })(file); reader.readAsDataURL(file); } });}/* * 将 image 对象 画入画布并导出base64数据 /export function convertImage( image, orientation, fileType = ‘jpeg’, maxSize = 200 * 1024, fillBgColor = ‘#ffffff’) { let maxWidth = 1280, maxHeight = 1280, cvs = document.createElement(‘canvas’), w = image.width, h = image.height, quality = 0.9; /* * 这里用于计算画布的宽高 / if (w > 0 && h > 0) { if (w / h >= maxWidth / maxHeight) { if (w > maxWidth) { h = (h * maxWidth) / w; w = maxWidth; } } else { if (h > maxHeight) { w = (w * maxHeight) / h; h = maxHeight; } } } let ctx = cvs.getContext(‘2d’); let size = prepareCanvas(cvs, ctx, w, h, orientation); // 填充白色背景 ctx.fillStyle = fillBgColor; ctx.fillRect(0, 0, size.w, size.h); //将图片绘制到Canvas上,从原点0,0绘制到w,h ctx.drawImage(image, 0, 0, size.w, size.h); let dataUrl = cvs.toDataURL(image/${fileType}, quality); //当图片大小 > maxSize 时,循环压缩,并且循环不超过5次 let count = 0; while (dataUrl.length > maxSize && count < 10) { let imgDataLength = dataUrl.length; let isDoubleSize = imgDataLength / maxSize > 2; // 质量一次下降 quality -= isDoubleSize ? COMPRESS_QUALITY_STEP_BIG : COMPRESS_QUALITY_STEP; quality = parseFloat(quality.toFixed(2)); // 图片还太大的情况下,继续压缩 。 按比例缩放尺寸 let scaleStrength = COMPRESS_SIZE_RATE; w = w * scaleStrength; h = h * scaleStrength; size = prepareCanvas(cvs, ctx, w, h, orientation); //将图片绘制到Canvas上,从原点0,0绘制到w,h ctx.drawImage(image, 0, 0, size.w, size.h); console.log(imgDataLength:${imgDataLength} , maxSize --&gt; ${maxSize}); console.log(size.w:${size.w}, size.h:${size.h}, quality:${quality}); dataUrl = cvs.toDataURL(image/jpeg, quality); count++; } console.log(imgDataLength:${dataUrl.length} , maxSize --&gt; ${maxSize}); console.log(size.w:${size.w}, size.h:${size.h}, quality:${quality}); cvs = ctx = null; return dataUrl;}/* * 准备画布 * cvs 画布 * ctx 上下文 * w : 想要画的宽度 * h : 想要画的高度 * orientation : 屏幕方向 /function prepareCanvas(cvs, ctx, w, h, orientation) { cvs.width = w; cvs.height = h; //判断图片方向,重置canvas大小,确定旋转角度,iphone默认的是home键在右方的横屏拍摄方式 let degree = 0; switch (orientation) { case 3: //iphone横屏拍摄,此时home键在左侧 degree = 180; w = -w; h = -h; break; case 6: //iphone竖屏拍摄,此时home键在下方(正常拿手机的方向) cvs.width = h; cvs.height = w; degree = 90; // w = w; h = -h; break; case 8: //iphone竖屏拍摄,此时home键在上方 cvs.width = h; cvs.height = w; degree = 270; w = -w; // h = h; break; } // console.log(orientation --&gt; ${orientation} , degree --&gt; ${degree}); // console.log(w --&gt; ${w} , h --&gt; ${h}); //使用canvas旋转校正 ctx.rotate((degree * Math.PI) / 180); return { w, h };}/* * 截取 ‘data:image/jpeg;base64,’, * 截取到第一个逗号 */export function removeBase64Header(content) { if (content.substr(0, 10) === ‘data:image’) { let splitIndex = content.indexOf(’,’); return content.substring(splitIndex + 1); } return content;}export function getFileType(fileName = ‘’) { return fileName.substring(fileName.lastIndexOf(’.’) + 1);}export function checkAccept( file, accept = ‘image/jpeg,image/jpg,image/png,image/gif’) { return accept.toLowerCase().indexOf(file.type.toLowerCase()) !== -1;}相关链接个人博客代码片段 ...

December 20, 2018 · 3 min · jiezi

chrome:移动端中的 100vh 始终等于地址栏隐藏时的高度

The first time when I know vh I was very excited. Finally, we can do this by css instead of js. However, still too naive.As we all know, scroll bar would hide automatically on mobile. So, it wouldn’t affect the layout like on desktop.However, the address bar would also hide when scrolling. Like images below:And the code is:<!DOCTYPE html><html lang=“en”> <head> <meta charset=“UTF-8” /> <meta name=“viewport” content=“width=device-width, initial-scale=1.0” /> <meta http-equiv=“X-UA-Compatible” content=“ie=edge” /> <title>Document</title> <style> * { margin: 0; padding: 0; } html, body, .app { /* height: 100%; / height: 100vh; } .app { width: 100%; border: 10px solid orange; box-sizing: border-box; position: relative; } .app__footer { height: 100px; width: 100%; box-sizing: border-box; border: 10px solid pink; position: absolute; bottom: 0; } </style> </head> <body> <div id=“app” class=“app”> <header class=“app__header”></header> <main class=“app__main”></main> <footer class=“app__footer”>app__footer</footer> </div> </body></html>The viewport size changes when scrolling. In my Mi6X, the smaller is 659px while the larger one is 715px when the address bar is hidden.According to test on chrome 70, height:100% and window.innerHeight is always equal to the smaller one. I think it is correct. I also thought 100vh would act like 100%. However, it’s not.According to developers.google the vh is always calculated as if the URL bar is hidden since Chrome version 56.So, 100vh is equal to the larger one which is 715px on my phone. That’s why images above would happen. In this case, if we use something like bottom:0; with 100vh we would meet situation like image one. Part of app__footer was covered. Instead, if we use height:100%, it won’t happen.However, as we all know it wouldn’t be possible to use 100% when we were in nested css modules. So, in this case, how can we get the 100% in nested css modules?Of course, we can save the 100% to rem like:document.documentElement.style.fontSize = window.innerHeight * 0.01 + ‘px’But I think the better way is using CSS_variables. For example:html,body,.app { / height: 100%; / / height: 100vh; */ height: calc(var(–vh) * 100);}document.documentElement.style.setProperty( ‘–vh’, window.innerHeight * 0.01 + ‘px’)Also, if you are worried about the compatibility. Here is the polyfill.Original PostReferencethe-trick-to-viewport-units-on-mobileCSS3 100vh not constant in mobile browser ...

December 5, 2018 · 2 min · jiezi