时间
时间戳(毫秒数)不分时区,即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=×tamp=&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 |
---|---|---|---|
ldpi | 240*320 | 120 | 0.75 |
mdpi | 320*480 | 160 | 1.0 |
hdpi | 480*800 | 240 | 1.5 |
xhdpi | 720*1280 | 320 | 2.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左右
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
一、未设置
默认为 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;
}
}