前端方案(时间/图片/PWA/微信公众号/图片前端压缩/动画与过渡/新兴方案/屏幕适配)

时间

时间戳(毫秒数)不分时区,即UTC时间所累积的毫秒数,UI必须获得时间戳或者UTC时间的字符串才能正确显示浏览器本地时间。

方案1、后台数据库存放本地时间,返回时间戳给UI。

             后台数据库存放本地时间,类型为timestamp或datetime,数据中存放和显示都为本地时间。但它转为时间戳时,还是它对应的UTC时间(本地时间减去时区)所累积的毫秒数。即本地时间对应的时间戳与UTC时间对应的时间戳是相同的,因为时间戳不分时区,都是UTC时间所累积的毫秒数。UI用new Date(时间戳),生成一个时间对象,这个对象的输出都是浏览器本地时间(毫秒数转成UTC时间,再加上时区),此时用format()就可以输出浏览器的本地时间。

方案2、后台数据库存放本地时间,返回服务器本地时间的字符串给UI
方案错误。UI不知道服务器的时区,无法算出时间戳,UI只能显示服务器的本地时间了。当浏览器跟服务器在同一个时区,那么结果正确。
方案3、后台数据库存放UTC时间,返回时间戳给UI
方案错误。数据库中存放UTC时间,转成时间戳时会减去时区,因此返回给UI的是一个错误的时间戳(实际的UTC时间减去服务器时区,所累积的毫秒数)。UI不知道服务器的时区,因此无法计算时间戳。当浏览器跟服务器在同一个时区,那么,UI把后台返回的时间加上时区,能得到正确的时间。
方案4、后台数据库存放UTC时间,返回时间字符串给UI
UI将返回的时间加上时区,得到浏览器本地时间。

new Date("2013-02-08T01:30:26.000Z") ISO 8601 相当于
new Date("2013-02-08 09:30:26 GMT+00:00")
new Date("2013-02-08 09:30:26") 相当于 new Date("2013-02-08 09:30:26 GMT+08:00")

时间处理库:moment.js

图片

 JPEG:有损压缩,线条和文字容易产生锯齿
PNG:无损压缩,支持透明度
GIF:无损压缩,支持动画,只支持256种颜色,只支持全透明

WebP:腾讯智图,图片压缩工具  智图客户端下载_图片压缩在线工具_在线制作webp
根据dpr区分下载图片:
1、background: image-set(url(foo.png) 1x,url(foo@2x.png) 2x);
2、媒体查询

exif.js

图片EXIF信息查看器:EXIF信息查看器

问题:iphone拍摄的照片,在电脑上旋转,后在网页中显示时方向出现变化

方案:

1、npm install exif-js --save

2、自动还原图片工具模块

import EXIF from 'exif-js';

//旋转图片到正常
const _rotateImg = (imgFile) => {
    return new Promise((resolve, reject) => {
        EXIF.getData(imgFile, function () {
            let exifTags = EXIF.getAllTags(this);
            let reader = new FileReader();
            reader.readAsDataURL(imgFile);
            reader.onload = e => {
                let imgData = e.target.result;
                // 8 表示 顺时针转了90
                // 3 表示 转了 180
                // 6 表示 逆时针转了90
                if (
                    exifTags.Orientation == 8 ||
                    exifTags.Orientation == 3 ||
                    exifTags.Orientation == 6
                ) {
                    //翻转
                    //获取原始图片大小
                    const img = new Image();
                    img.src = imgData;
                    img.onload = function () {
                        let cvs = document.createElement('canvas');
                        let ctx = cvs.getContext('2d');
                        //如果旋转90
                        if (
                            exifTags.Orientation == 8 ||
                            exifTags.Orientation == 6
                        ) {
                            cvs.width = img.height;
                            cvs.height = img.width;
                        } else {
                            cvs.width = img.width;
                            cvs.height = img.height;
                        }
                        if (exifTags.Orientation == 6) {
                            //原图逆时针转了90, 所以要顺时针旋转90
                            ctx.rotate(Math.PI / 180 * 90);
                            ctx.drawImage(
                                img,
                                0,
                                0,
                                img.width,
                                img.height,
                                0,
                                -img.height,
                                img.width,
                                img.height
                            );
                        }
                        if (exifTags.Orientation == 3) {
                            //原图逆时针转了180, 所以顺时针旋转180
                            ctx.rotate(Math.PI / 180 * 180);
                            ctx.drawImage(
                                img,
                                0,
                                0,
                                img.width,
                                img.height,
                                -img.width,
                                -img.height,
                                img.width,
                                img.height
                            );
                        }
                        if (exifTags.Orientation == 8) {
                            //原图顺时针旋转了90, 所以要你时针旋转90
                            ctx.rotate(Math.PI / 180 * -90);
                            ctx.drawImage(
                                img,
                                0,
                                0,
                                img.width,
                                img.height,
                                -img.width,
                                0,
                                img.width,
                                img.height
                            );
                        }
                        resolve(cvs.toDataURL('image/png'));
                    }
                }
                else {
                    resolve(imgData);
                }
            }
        });
    });
}

export default _rotateImg;

PWA

Progressive Web APP 渐进式Web应用:用于实现离线加载能力、离线使用能力、消息推送能力的一套技术方案

APP化:图标添加到主屏,全屏运行

应用缓存:Application cache,由于编程能力差、无法清理缓存、没有路由机制,将被废弃

缓存控制:service worker + cache storage

本地存储:local storage、session storage

本地数据库:Web SQL、IndexedDB

服务器端推送:SSE、web socket

客户端推送:Web Notification

Servcie worker + Cache storage

self

主窗口中,self == window = parent

iframe中,self == window != parent

在web worker 和 service worker中,没有window,全局对象是 self

主线程中注册service worker脚本:navigator.serviceWorker.register('./serviceWorker.js');

service worker线程生命周期:installing -> installed -> activating -> activated

service worker脚本中监听事件

//service worker被安装,这时添加缓存cache.open(cacheName) -> cache.addAll(fileNames)

slef.addEventListener("install", function) 

// 当前页面的service work被激活,这时可以删除以往的缓存caches.delete(cacheName)

slef.addEventListener("activate", function)

// 请求文件,这时可以直接返回已经缓存的文件event.responseWith() -> caches.match(event.request)

slef.addEventListener("fetch", function)

Web Notification

Notification.requestPermission()   // 向用户请求允许

Notification.permission               // 用户允许状态

new Notification(title, options)  // 推送消息

Notification.close()                    // 关闭消息

微信公众号

微信公众号网页开发

授权流程
1、设置授权回调域名:微信公众平台-接口权限-网页授权获取用户基本信息
2、自定义菜单:微信公众平台-自定义菜单  或者 微信公众平台接口调试工具-发送请求
方案一:菜单的URL设置为 https://open.weixin.qq.com/connect/oauth2/authorize?appid=&redirect_uri=&response_type=code&scope=snsapi_userinfo#wechat_redirect
URL中包含 appId 和 授权后要跳转到的URL(encodeURIComponent 处理后)
方案二:菜单的URL设置为 简单地址,后端判断是否已授权,未授权则重定向到方案一的地址
3、引导用户访问以上URL,显式授权:出现授权页面,点击授权;静默授权:用户已关注该公众号,不出现授权页面
4、跳转到redirect_uri/?code=10003
5、公众号后端请求 https://api.weixin.qq.com/sns/oauth2/access_token?appid=&secret=&code=&grant_type=authorization_code
URL中包含 appId,开发者密码secret,从4中得到的code
得到 {"access_token":"授权凭证", "openid":"用户标识" }
6、公众号后端请求 https://api.weixin.qq.com/sns/userinfo?access_token=&openid=&lang=zh_CN
URL中包含 5中得到的 access_token 和 openid
得到用户基本信息

JS-SDK调用流程
1、设置JS接口安全域名:微信公众平台-公众号设置-功能设置-JS接口安全域名
2、公众号后端请求 https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=&type=jsapi
URL中包含 access_token,得到jsapi_ticket { "ticket":""}
3、公众号后端生产 随机字符串noncestr, 时间戳timestamp, 当前网页的url(不包含#及其后面部分)
4、拼接成jsapi_ticket=&noncestr=&timestamp=&url=,计算sha1(拼接值),得到签名signature,返给前端
5、前端引入JS-SDK:http://res.wx.qq.com/open/js/jweixin-1.2.0.js
6、前端调用前配置
wx.config({
    debug: true, 
    appId: '',
    timestamp: ,
    nonceStr: '',
    signature: '',
    jsApiList: [需要使用的JS接口列表]
});
7、
wx.ready(function(){
    // SDK接口调用
});
 

微信支付流程

一、设置支付目录:微信商户平台-商户平台-->产品中心-->开发配置

二、下单
1、前端下单,后端调用微信支付系统下单API,得到prepay_id
2、后端生成 时间戳timestamp,随机字符串nonceStr,支付签名paySign,和prepay_id一起返给前端
三、支付
1、调用支付接口
wx.chooseWXPay({
timestamp: 0, 
nonceStr: '',
package: '', // prepay_id
paySign: '',
success: function (res) {
// 支付成功后的回调函数
}
});
2、用户确认支付,微信支付系统给微信客户端返回支付信息;同时异步通知后端支付结果
四、展示支付结果
1、前端支付接口成功回调,向后端查询支付结果,后端未有结果,则向微信支付系统查询
2、前端展示支付结果

微信网页对比浏览器网页

微信网页支持微信的很多能力,例如扫描二维码、支付

图片前端压缩

 基础知识

Data URL
格式:data:image/jpeg;base64,Base64字符串
      data:text/csv;charset=utf-8,内容

来源:FileReader.readAsDataURL()、canvas.toDataURL()、btoa()
      
Blob URL
格式:blob:http://127.0.0.1:8090/f0e5d6f6-a92c-4059-8506-28ca72fcb632

Blob 对象
来源:new Blob([ArrayBuffer])、new Blob([ArrayBuffer视图])、canvas导出
功能:存储二进制数据,被FileReader读取
属性:size,type
方法:
    slice:剪切

File 对象
继承:file.__proto__=File,file.constructor=File,File.__proto__=Blob
来源:文件选择框、new File([blob], name)
属性:name,type,size

FileReader 对象
来源:new FileReader
功能:读取Blob 对象
方法:
    readAsDataURL:读取成base64 Data URL,可被<img src>引用
    readAsBinaryString:读取成BinaryString
    readAsArrayBuffer:读取成ArrayBuffer对象

BinaryString
功能:用单字节字符来表示一个字节,单字节字符组成的字符串来表示二进制数据
来源:FileReader.readAsBinaryString()、atob(DataUrl.split(",")[1])

ArrayBuffer对象(类型化数组)
功能:即内存中的一段固定长度的空间
来源:new ArrayBuffer(length)、FileReader.readAsArrayBuffer()
属性:byteLength

ArrayBufferView(以 Uint8Array 为例)
功能:ArrayBuffer的视图,将ArrayBuffer的表示成一个数组,数组的元素为ArrayBuffer中8比特(一个字节)所对应的一个无符号整数
其他视图:Int8Array(8比特有符号整数数组)、Uint16Array(16比特无符号整数数组)、Uint32Array(32比特无符号整数数组)等
来源:new Uint8Array(arrayBuffer); 
for (let i = 0; i < BinaryString.length; i++) {
    arrayBufferView[i] = BinaryString.charCodeAt(i);
}


FormData 对象
来源:new FormData() 或 new FormData(<form>元素);
功能:构造表单数据,被 XMLHttpRequest.send()发出
方法:
    append:添加字段,可以添加string、blob、file
    delete:删除字段

Image 对象
继承:img.__proto__=HTMLImageElement,即 <img>。另外img.constructor=HTMLImageElement,img.constructor不是其构造器Image,是其构造器的prototype.constructor
来源:new Image()
属性:src,height,width

HTMLCanvasElement 对象
来源:document.createElement('canvas')
功能:绘制图形,导出图形数据
方法:
    toDataURL:导出成Base64字符串,可被<img>的src引用
    toBlob:导出Blob对象
    
URL
方法:
    createObjectURL(blob):构造Blob URL;可以被 <a href> ,用于把前端生成的数据作为文件来下载;用于<img src>显示大图片时,比Data URL性能更好
    URL.revokeObjectURL(url):释放Blob URL对应的文件数据

前端图片压缩过程


1、<input type=file> 选取文件得到 file
2、用fileReader读取file,得到Data URL
3、用Data URL构造成 image
4、用image绘制特定尺寸的Canvas
5、Canvas导出blob 用于上传,或导出 Data URL 用于显示

压缩后上传
1、将blob转成新file,new File([blob], 原file.name),这一步可选
2、用formData包装 新file,最终 XMLHttpRequest.send(formData);

function compressImp(file, resolve, reject)
{
    if (file.type.indexOf("image") === 0) {
        let reader = new FileReader(),
            img = new Image();

        // 将 file 读取成 Data url
        reader.readAsDataURL(file);
        reader.onload = function (e) {
            // 用 Data url 构造 image
            img.src = e.target.result;
        };

        img.onload = function () {
            let canvas = document.createElement('canvas');
            let context = canvas.getContext('2d');

            // 图片原始尺寸
            let originWidth = this.width;
            let originHeight = this.height;

            // 最大尺寸限制,可通过设置宽高来实现图片压缩程度
            let maxWidth = 300,
                maxHeight = 300;
            // 目标尺寸
            let targetWidth = originWidth,
                targetHeight = originHeight;
            // 图片尺寸超过300x300的限制
            if (originWidth > maxWidth || originHeight > maxHeight) {
                if (originWidth / originHeight > maxWidth / maxHeight) {
                    // 更宽,按照宽度限定尺寸
                    targetWidth = maxWidth;
                    targetHeight = Math.round(maxWidth * (originHeight / originWidth));
                } else {
                    targetHeight = maxHeight;
                    targetWidth = Math.round(maxHeight * (originWidth / originHeight));
                }
            }
            // canvas对图片进行缩放
            canvas.width = targetWidth;
            canvas.height = targetHeight;
            // 清除画布
            context.clearRect(0, 0, targetWidth, targetHeight);
            // 用image 绘制 canvas
            context.drawImage(img, 0, 0, targetWidth, targetHeight);

            // 非 IE,返回 File对象
            if(canvas.toBlob){
                canvas.toBlob((blob)=>{
                    // blob 转 file,因为blob没有文件名信息
                    let newFile = new File([blob], file.name);
                    resolve(newFile);
                }, 'image/jpeg', 0.92);
            }
            else{  // 兼容IE,返回Base64,IE下无法构造File对象
                let dataUrl = canvas.toDataURL('image/jpeg', 0.92);
                resolve(dataUrl);
            }
        };
    }
}
export default function compress(file){
    return new Promise(function(resolve, reject) {
        compressImp(file, resolve, reject)
    });
}

动画 与 过渡

 CSS3 transition 过渡


缓动效果:线性、慢进、慢出、贝塞尔曲线cubic-bezier
Vue中, 使用<transition>可以在 元素插入和移除时,添加过渡效果

CSS3 animation 关键帧动画


效果:可以设置多个关键帧
Vue中, 使用<transition>可以在 元素插入和移除时,添加关键帧动画

使用动画:animation: myfirst 5s;    参数为动画名和动画时间

定义动画:

                         @keyframes myfirst
                         {
                                from   {background: red;}
                                50%  {background: blue;}
                                to  {background: green;}
                         }

动画循环:animation-iteration-count(循环次数),animation-direction(是否往返循环)

动画事件:
                                

element.addEventListener("animationstart", listener, false);         // 监听动画开始事件,listener为事件处理函数
element.addEventListener("animationend", listener, false);          //动画结束
element.addEventListener("animationiteration", listener, false);  //动画新一周期开始

Velocity.js  脚本动画


效果:链式动画(给一个元素连续添加多个动画)、循环、回放、各种缓动曲线
Vue中,<transition>的钩子(元素插入前事件、元素插入后事件、元素移除前事件、元素移除后事件)可以调用Velocity.js,实现脚本动画
Velocity 缓动
1、预设的缓动曲线:参考 https://easings.net/  ,包括 反弹、起跳等效果
2、CSS3 transition 支持的缓动曲线,即慢近慢出、贝塞尔曲线
3、物理学弹性效果:缓动模式设置成'spring',或者具体的[tension(张力),friction(摩擦力)]
4、跳跃效果:缓动模式设置成 [steps(步数)]

注意:

针对同一个元素的同一个样式,跟transition不能叠用

有些transitio支持的过渡,Velocity不支持,比如背景颜色

Animate.css 预设的动画样式


场景:原地晃动、渐入渐出

缓动效果:弹跳、显隐、滑动、滚动、扭动、旋转、翻转、缩放、摆动
Vue中,给<transition>加上Animate.css的类,即可应用动画

注意:

同一个位置,元素A滚出,同时元素B滚入;元素A滚出前,要置为绝对定位,否则会占住位置。

对比选型:

简单过渡用transition

只考虑原地晃动 和 渐入渐出,使用Animate.css

在一定范围内运动,使用Velocity.js

GSAP 动画库
 

    let animate1 = gsap.to(mesh.position, {
        x: 300,
		duration: 5,
		ease: "bounce.inOut", // 速度曲线
		delay: 2,
		repeat: 2,
		yoyo: true,   // 往返
		onStart: ()=>{
            console.log('动画开始');
		},
		onComplete: () => {
            console.log('动画结束');
        }
    });

新兴方案

React Native:Facebook推出的Native App方案,使用React框架开发

Weex:阿里推出的Native App方案,使用Vue框架开发

Flutter:Google推出的Native App方案,使用Dart语言开发

Hummer:滴滴推出的轻量级跨端技术框架,支持Vue 和 React 开发

WebAssembly:将编译型语言编译成WebAssembly,从而能在浏览器运行

Serverless:云平台提供的FaaS(Functions as a Service) 和 BaaS(Backend as a Service),减少后端程序和运维

FaaS:函数服务化,即运行函数的平台,例如函数计算服务,FaaS可以调用BaaS

BaaS:后端服务化,例如CDN服务、对象存储服务、日志服务、云监控、云数据库、消息队列

小程序云开发:小程序代码中直接调用FaaS和BaaS,例如操作数据库。开发简单应用甚至可以不用后端程序

中台:通用能力的平台化

技术中台:后端技术、运维技术的平台化,例如微服务开发框架、Devops平台、PaaS平台、容器云等
业务中台:业务的平台化(微服务化),例如用户中心、订单中心等
组织中台:企业内部组织的平台化,例如资源调度中心、内部创新孵化组织

屏幕适配

参考
https://github.com/amfe/article/issues/17
https://github.com/amfe/lib-flexible
http://www.cocoachina.com/webapp/20150715/12585.html

方法

弹性布局:flex、grid、居中、calc()
样式选择:媒体查询、媒体信息作为选择器前缀(例如<html [dpr=2]>)
相对尺寸:%,rem(设置<html font-size=dip数/10>)、vw、rpx
自适应总体方案:高度用px、字体大小用px、宽度用vw或rem、rpx、calc()

像素

px(pixels):物理像素,硬件像素,设备像素,显示像素
dp,dip(deveice independent pixels):逻辑像素、设备无关像素、CSS像素
pt(point):1/72英寸
dpr(devicePixelRatio): 设备像素比,即 1dip包含了多少个px,即px数 / dip数
ppi,dpi:像素密度(像素每英寸),斜向一英寸里的物理像素数量
em: 相对于父元素的font-size的相对单位。
rem: 相对于根元素的font-size的相对单位。
vw: 1% 布局视口宽度
vh: 1% 布局视口高度
resolution:分辨率,横向物理像素数 * 纵向物理像素数

屏幕

设备尺寸列表 https://material.io/devices/

ppi与默认dpr对应关系

^显示分辨率ppi默认dpr
ldpi240*3201200.75
mdpi320*4801601.0
hdpi480*8002401.5
xhdpi720*12803202.0
Retina屏(视网膜屏): ppi 大于320

案例 – iPhone5
resolution:640 * 1136,屏幕尺寸:4英寸
ppi = 开根号(1136 * 1136 + 604 * 640) / 4 = 326。默认dpr:2
逻辑像素:640 * 1136 / 2 = 320 * 568

总结
屏幕宽度dip数,即宽度px数 / dpr,都在360左右

JS读取屏幕信息

screen.width 屏幕的dip数
window.innerWidth 浏览器的dip数(包含滚动条占据的宽度)
document.documentElement.clientWidth viewport的dip数
document.documentElement.offsetWidth 根元素的dip数
window.devicePixelRatio 屏幕dpr
orientationchange 屏幕转向事件

视口

屏幕尺寸 = px尺寸 * px数 = dip尺寸 * dip数 = px尺寸 * dpr * dip数
视口dip数:布局宽度,即document.documentElement.clientWidth,与窗口的宽度window.innerWidth可以不同

viewport 设置

<meta name="viewport" content="width=deveice-width,initial-scale=1, mininum-scale=,maximum-scale=,user-scalable=no">

width:视口的dip数,相当一部分手机浏览器默认为 980px
deveice-width:屏幕的默认dip数,即px数 / 默认dpr
initial-scale:初始缩放比例,默认值为auto,即缩放到屏幕尺寸
user-scalable:是否允许手动缩放
mininum-scale:最小手动缩放比例
maximum-scale:最大手动缩放比例

缩放实践

一、桌面浏览器,设置viewport缩放,无效

二、桌面浏览器,手动缩小,就是改变dpr:
1、dpr变小,视口dip数变大
2、相对(于视口)宽度的元素,尺寸不变,dip数变大。
3、固定宽度的元素,dip数不变,尺寸变小。
4、文字无法无限缩小,缩小到一定程度后继续缩小,尺寸不变,dip数变大。
桌面浏览器的window.devicePixelRatio 与 实际dpr 相等

三、桌面浏览器,手动放大,与手动缩小效果相反

四、移动浏览器手动缩放
视口、百分比宽度元素、固定宽度元素,全都是尺寸缩放,dip数不变。视口尺寸不能缩小到比屏幕小。

四、移动浏览器,设置initial-scale放大(initial-scale大于1)
1、有些浏览器无效
2、效果与手动放大相同

五、移动浏览器,设置initial-scale缩小(initial-scale为auto或小于1)
1、实际dpr变小,视口dip数不变
2、相对(于视口)宽度的元素,dip数不变,尺寸变小。
3、固定宽度的元素,dip数不变,尺寸变小。
4、文字缩小到不能再缩小的程度
5、当视口缩小到device-width后,改为视口dip数变大(有极限),尺寸不变
自动缩小后,实际dpr与默认dpr不再相等,window.devicePixelRatio 仍然等于 默认dpr

viewport 设置案例

一、未设置
默认为 width=980px,initial-scale=auto

二、viewport只设置width,不设置initial-scale
默认为 initial-scale=auto
使用场景:设计稿宽为750px(iPhone6),可以设置width=750px,自动缩放后,所有元素等比例缩小

三、viewport只设置initial-scale=1,不设置width
默认为 width=device-width

四、viewport只设置initial-scale !=1,不设置width
那么 Android width=980px,IOS width=device-width

五、viewport设置 width=device-width, initial-scale=1
那么布局宽度约为360px,这时候实际dpr 正好等于 默认dpr

屏幕适配方案

viewport方案一

<meta name="viewport" content="width=750px, user-scalable=no">

width=设计稿的宽度,initial-scale=auto
缺点:自动缩放,不够精细

viewport方案二

<meta name="viewport" content="width=deveice-width, initial-scale=1, user-scalable=no">

缺点:不能解决border:1px 问题

viewport方案三
初始viewport: <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
重设viewport:

let defaultDpr = window.devicePixelRatio || 1;   // 默认dpr
let portWidth = document.documentElement.clientWidth * defaultDpr;   // 物理像素数
let viewport = document.querySelector('meta[name="viewport"]');
viewport.setAttribute('content', 'width=' + portWidth  + ', initial-scale=' + 1 / defaultDpr + ',user-scalable=no');
// 或者 viewport.setAttribute('content', 'width=device-width, initial-scale='+ 1 / defaultDpr  + ',user-scalable=no');
// 由 上文 “缩放实践-五” 可以得出以上两行效果是一样的,效果都是 视口dip数 等于 px数

相对宽度方案

  let remSize = (document.documentElement.clientWidth / 10).toFixed(2);
  document.documentElement.style.fontSize = remSize + "px";
  window.remSize = remSize;
  window.rem2px = function(remNum) {
      return parseFloat(remNum) * remSize;
  };
  window.px2rem: function(pxNum) {
      return parseFloat(pxNum) / remSize;
  };

使用vw 或 rem,rem比vw兼容性好

n倍图方案

 let defaultDpr = window.devicePixelRatio || 1;
 document.documentElement.setAttribute('data-dpr',  window.devicePixelRatio );
 window.defaultDpr  = defaultDpr;

n倍图应用于默认dpr为n的屏幕,根据不同[data-dpr] 引用不同@的图片
2倍

设计稿px 转 css单位 方案
以750px设计稿为例,对应的默认dpr为2

   // px 转 rem,适用于相对宽度的元素
    .px2rem(@name, @px){
        @{name}: @px / 75 * 1rem;
    }
    
// px 转 px,适用于固定宽度的元素,例如字体、1px边框
// 前提为采用了 viewport方案三
.px2px(@name, @px){
    @{name}: round(@px / 2) * 1px;
    [data-dpr="2"] & {
        @{name}: @px * 1px;
    }
    [data-dpr="2.5"] & {
        @{name}: round(@px * 2.5 / 2) * 1px;
    }
    [data-dpr="2.75"] & {
        @{name}: round(@px * 2.75 / 2) * 1px;
    }
    [data-dpr="3"] & {
        @{name}: round(@px / 2 * 3) * 1px
    }
    [data-dpr="4"] & {
        @{name}: @px * 2px;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值