!https://github.com/journey-ad/pixel-paint
又是一个练手用的Web App,依然使用Vue进行开发。在此把遇到的一些场景和解决方法一并记录下来
移动端弹出框滚动穿透
有两个解决方法:
弹窗组件最外层添加
touchmove
事件监听,并阻止默认行为,但会阻止所有滚动事件,仅当弹框内不需要滚动时使用body
设置position: fixed
,可能影响布局,酌情使用
移动端滑动选择(模拟:hover)
移动端原生不支持滑动触发状态,touchmove
回调传入的Touch事件对象target
始终为触摸开始时的元素,但Touch事件对象包含pageX
、pageY
、clientX
、clientY
等属性,值为滑动坐标偏移值,结合父容器的clientWidth
和元素数量可计算出当前滑动元素的索引,进行相应处理
滑动事件触发频率较高,易产生性能问题,通常使用节流函数限制触发频次
元素移动性能
使用transform
的translate
函数移动元素位置,而不是top、left、margin等,后者会触发页面回流,消耗更多性能
还可使用translate3d
开启硬件加速进一步提高性能
移动端禁止下拉刷新
方法一
对body
应用css样式overscroll-behavior: none;
方法二
使body
和容器脱离文档流,并设置元素堆叠
.body,
.wrapper {
/* 脱离文档流 */
position: absolute;
top: 0px;
/* 占满空间 */
width: 100%;
height: 100%;
/* 移除边距 */
margin: 0;
/* 允许纵向滚动 */
overflow-y: hidden;
}
.body {
/* body在底部 */
z-index: 1;
}
.wrapper {
/* 容器在上部 */
z-index: 2;
}
Canvas性能实践
使用requestAnimationFrame
setInterval
可能会被阻塞造成的回调执行周期不可控,且无法应对刷新率不同的设备,requestAnimationFrame
可以解决这两个问题,回调执行周期严格控制在浏览器发生刷新之前,且随设备刷新率变化而动态发生改变,这个周期在60Hz的屏幕上为1000/60 ≈ 16.67ms
使用cancelAnimationFrame
取消raf
Canvas分层
对于静态或更新频率较低的画面可以放到另外一层画布上,画布间使用z-index
控制堆叠顺序,可以不必与需要频繁更新的画布一同绘制,减少性能开销,这种做法在游戏领域较为常见
使用“离屏绘制”
Canvas无需插入页面也能绘制,结合document.createElement()
与ctx.drawImage()
方法,维护一个小尺寸的画布作为原始数据,画板会更容易实现缩放、平移等功能
Canvas禁用自动平滑(使用临近缩放)
对于像素画风的应用或游戏很有用,绘制前设置ctx.imageSmoothingEnabled = false
,由于此属性仍为实验性质,使用时要加上前缀
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
随机数版本uuid生成JS实现
let uuid = () => {
let d = Date.now()
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
d += performance.now()
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = (d + Math.random() * 16) % 16 | 0
d = Math.floor(d / 16)
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
})
}
生成随机十六进制色值
getRandomColor() {
let result = [];
for (let i = 0; i < 16; i++) {
result.push("#" + ((Math.random() * 0xffffff) << 0).toString(16));
}
return result;
}
node.js相关
图片裁剪、缩放
const sharp = require('sharp')
sharp('path/to/image.png')
.extract({
left: 0,
top: 100,
width: 1080,
height: 1080
})
.resize(128, 128, {
kernel: 'nearest' // 临近算法
})
.toBuffer((_err, data, info) => {
console.log(info, data)
})
简单命令行参数获取
const argv = require('optimist').argv
console.log(argv)
获取图片像素信息
const getPixels = require('get-pixels')
getPixels('path/to/image.png', function (_err, pixels) {
let pixels = []
for (let y = 0; y < pixels.shape[1]; y++) {
for (let x = 0; x < pixels.shape[0]; x++) {
const r = pixels.get(x, y, 0).toString(16)
const g = pixels.get(x, y, 1).toString(16)
const b = pixels.get(x, y, 2).toString(16)
const hex = `#${r}${g}${b}`
pixels.push(hex)
}
}
})
Vue-Cli 3.x使用moment.js
、lodash
打包精简
安装webpack-bundle-analyzer
和lodash-webpack-plugin
,配置vue.config.js
文件
let webpack = require('webpack')
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
let LodashModuleReplacementPlugin = require('lodash-webpack-plugin')
module.exports = {
chainWebpack: config => {
config.plugin('ignore')
.use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/))
config.plugin('analyzer')
.use(new BundleAnalyzerPlugin())
config.plugin('loadshReplace')
.use(new LodashModuleReplacementPlugin())
}
}
Vue-Router按需加载页面,JS分包
组件定义为promise并import实现按需加载
使用魔术注释/* webpackChunkName: "chunkName" */
实现JS分包
import Vue from 'vue'
import Router from 'vue-router'
const Artwork = (resolve) => {
import(/* webpackChunkName: "artwork" */ './views/artwork.vue').then((module) => {
resolve(module)
})
}
Vue.use(Router)
export default new Router({
routes: [
{
path: '/artwork',
name: 'artwork',
component: Artwork
}
]
})