优化文字移动性能

This commit is contained in:
0JARVIS0 2019-11-18 14:08:07 +08:00
parent d85be378c4
commit b613dd610b
2 changed files with 200 additions and 111 deletions

View File

@ -9,17 +9,23 @@ export default class Painter {
this.globalHeight = {}; this.globalHeight = {};
} }
paint(callback) { isMoving = false
movingCache = {}
paint(callback, isMoving, movingCache) {
this.style = { this.style = {
width: this.data.width.toPx(), width: this.data.width.toPx(),
height: this.data.height.toPx(), height: this.data.height.toPx(),
}; };
if (isMoving) {
this.isMoving = true
this.movingCache = movingCache
}
this._background(); this._background();
for (const view of this.data.views) { for (const view of this.data.views) {
this._drawAbsolute(view); this._drawAbsolute(view);
} }
this.ctx.draw(false, () => { this.ctx.draw(false, () => {
callback && callback(); callback && callback(this.callbackInfo);
}); });
} }
@ -420,6 +426,7 @@ export default class Painter {
this._doBorder(view, width, height); this._doBorder(view, width, height);
} }
callbackInfo = {}
_fillAbsText(view) { _fillAbsText(view) {
if (!view.text) { if (!view.text) {
return; return;
@ -434,77 +441,16 @@ export default class Painter {
height, height,
extra, extra,
} = this._preProcess(view, view.css.background && view.css.borderRadius); } = this._preProcess(view, view.css.background && view.css.borderRadius);
if (this.isMoving && JSON.stringify(this.movingCache) !== JSON.stringify({})) {
this.ctx.fillStyle = (view.css.color || 'black'); this.globalWidth[view.id] = this.movingCache.globalWidth
const { this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left');
lines, for (const i of this.movingCache.lineArray) {
lineHeight, const {
textArray, measuredWith,
linesArray, text,
} = extra; x,
// 如果设置了id则保留 text 的长度 y,
if (view.id) { } = i
let textWidth = 0;
for (let i = 0; i < textArray.length; ++i) {
textWidth = this.ctx.measureText(textArray[i]).width > textWidth ? this.ctx.measureText(textArray[i]).width : textWidth;
}
this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth;
}
let lineIndex = 0;
for (let j = 0; j < textArray.length; ++j) {
const preLineLength = Math.round(textArray[j].length / linesArray[j]);
let start = 0;
let alreadyCount = 0;
for (let i = 0; i < linesArray[j]; ++i) {
// 绘制行数大于最大行数,则直接跳出循环
if (lineIndex >= lines) {
break;
}
alreadyCount = preLineLength;
let text = textArray[j].substr(start, alreadyCount);
let measuredWith = this.ctx.measureText(text).width;
// 如果测量大小小于width一个字符的大小则进行补齐如果测量大小超出 width则进行减除
// 如果已经到文本末尾,也不要进行该循环
while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) {
if (measuredWith < width) {
text = textArray[j].substr(start, ++alreadyCount);
} else {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = textArray[j].substr(start, --alreadyCount);
}
measuredWith = this.ctx.measureText(text).width;
}
start += text.length;
// 如果是最后一行了,发现还有未绘制完的内容,则加...
if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) {
while (this.ctx.measureText(`${text}...`).width > width) {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = text.substring(0, text.length - 1);
}
text += '...';
measuredWith = this.ctx.measureText(text).width;
}
this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left');
let x;
switch (view.css.textAlign) {
case 'center':
x = 0;
break;
case 'right':
x = (width / 2);
break;
default:
x = -(width / 2);
break;
}
const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight));
lineIndex++;
if (view.css.textStyle === 'stroke') { if (view.css.textStyle === 'stroke') {
this.ctx.strokeText(text, x, y, measuredWith); this.ctx.strokeText(text, x, y, measuredWith);
} else { } else {
@ -513,23 +459,130 @@ export default class Painter {
const fontSize = view.css.fontSize.toPx(); const fontSize = view.css.fontSize.toPx();
if (view.css.textDecoration) { if (view.css.textDecoration) {
this.ctx.beginPath(); this.ctx.beginPath();
if (/\bunderline\b/.test(view.css.textDecoration)) { this.ctx.moveTo(...this.callbackInfo.textDecoration.moveTo);
this.ctx.moveTo(x, y); this.ctx.lineTo(...this.callbackInfo.textDecoration.lineTo);
this.ctx.lineTo(x + measuredWith, y);
}
if (/\boverline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize);
this.ctx.lineTo(x + measuredWith, y - fontSize);
}
if (/\bline-through\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize / 3);
this.ctx.lineTo(x + measuredWith, y - fontSize / 3);
}
this.ctx.closePath(); this.ctx.closePath();
this.ctx.strokeStyle = view.css.color; this.ctx.strokeStyle = view.css.color;
this.ctx.stroke(); this.ctx.stroke();
} }
} }
} else {
this.ctx.fillStyle = (view.css.color || 'black');
const {
lines,
lineHeight,
textArray,
linesArray,
} = extra;
// 如果设置了id则保留 text 的长度
if (view.id) {
let textWidth = 0;
for (let i = 0; i < textArray.length; ++i) {
textWidth = this.ctx.measureText(textArray[i]).width > textWidth ? this.ctx.measureText(textArray[i]).width : textWidth;
}
this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth;
if (!this.isMoving) {
Object.assign(this.callbackInfo, {
globalWidth: this.globalWidth[view.id]
})
}
}
let lineIndex = 0;
for (let j = 0; j < textArray.length; ++j) {
const preLineLength = Math.round(textArray[j].length / linesArray[j]);
let start = 0;
let alreadyCount = 0;
for (let i = 0; i < linesArray[j]; ++i) {
// 绘制行数大于最大行数,则直接跳出循环
if (lineIndex >= lines) {
break;
}
alreadyCount = preLineLength;
let text = textArray[j].substr(start, alreadyCount);
let measuredWith = this.ctx.measureText(text).width;
// 如果测量大小小于width一个字符的大小则进行补齐如果测量大小超出 width则进行减除
// 如果已经到文本末尾,也不要进行该循环
while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) {
if (measuredWith < width) {
text = textArray[j].substr(start, ++alreadyCount);
} else {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = textArray[j].substr(start, --alreadyCount);
}
measuredWith = this.ctx.measureText(text).width;
}
start += text.length
// 如果是最后一行了,发现还有未绘制完的内容,则加...
if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) {
while (this.ctx.measureText(`${text}...`).width > width) {
if (text.length <= 1) {
// 如果只有一个字符时,直接跳出循环
break;
}
text = text.substring(0, text.length - 1);
}
text += '...';
measuredWith = this.ctx.measureText(text).width;
}
this.ctx.setTextAlign(view.css.textAlign ? view.css.textAlign : 'left');
let x;
switch (view.css.textAlign) {
case 'center':
x = 0;
break;
case 'right':
x = (width / 2);
break;
default:
x = -(width / 2);
break;
}
const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight));
lineIndex++;
if (view.css.textStyle === 'stroke') {
this.ctx.strokeText(text, x, y, measuredWith);
} else {
this.ctx.fillText(text, x, y, measuredWith);
}
const fontSize = view.css.fontSize.toPx();
if (view.css.textDecoration) {
this.ctx.beginPath();
if (/\bunderline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + measuredWith, y);
!this.isMoving && (this.callbackInfo.textDecoration = {
moveTo: [x, y],
lineTo: [x + measuredWith, y]
})
}
if (/\boverline\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize);
this.ctx.lineTo(x + measuredWith, y - fontSize);
!this.isMoving && (this.callbackInfo.textDecoration = {
moveTo: [x, y - fontSize],
lineTo: [x + measuredWith, y - fontSize]
})
}
if (/\bline-through\b/.test(view.css.textDecoration)) {
this.ctx.moveTo(x, y - fontSize / 3);
this.ctx.lineTo(x + measuredWith, y - fontSize / 3);
!this.isMoving && (this.callbackInfo.textDecoration = {
moveTo: [x, y - fontSize / 3],
lineTo: [x + measuredWith, y - fontSize / 3]
})
}
this.ctx.closePath();
this.ctx.strokeStyle = view.css.color;
this.ctx.stroke();
}
if (!this.isMoving) {
this.callbackInfo.lineArray ? this.callbackInfo.lineArray.push({ text, x, y, measuredWith }) : this.callbackInfo.lineArray = [{ text, x, y, measuredWith }]
}
}
}
} }
this.ctx.restore(); this.ctx.restore();
this._doBorder(view, width, height); this._doBorder(view, width, height);

View File

@ -13,6 +13,7 @@ Component({
canvasHeightInPx: 0, canvasHeightInPx: 0,
paintCount: 0, paintCount: 0,
currentPalette: {}, currentPalette: {},
movingCache: {},
/** /**
* 组件的属性列表 * 组件的属性列表
*/ */
@ -22,7 +23,7 @@ Component({
}, },
palette: { palette: {
type: Object, type: Object,
observer: function (newVal, oldVal) { observer: function(newVal, oldVal) {
if (this.isNeedRefresh(newVal, oldVal)) { if (this.isNeedRefresh(newVal, oldVal)) {
this.paintCount = 0; this.paintCount = 0;
this.startPaint(); this.startPaint();
@ -31,7 +32,7 @@ Component({
}, },
dancePalette: { dancePalette: {
type: Object, type: Object,
observer: function (newVal, oldVal) { observer: function(newVal, oldVal) {
if (!this.isEmpty(newVal)) { if (!this.isEmpty(newVal)) {
this.initDancePalette(newVal); this.initDancePalette(newVal);
} }
@ -48,7 +49,7 @@ Component({
}, },
action: { action: {
type: Object, type: Object,
observer: function (newVal, oldVal) { observer: function(newVal, oldVal) {
if (newVal) { if (newVal) {
this.doAction(newVal) this.doAction(newVal)
} }
@ -82,7 +83,7 @@ Component({
return true; return true;
}, },
doAction(newVal) { doAction(newVal, callback, isMoving) {
if (newVal && newVal.id && this.touchedView.id !== newVal.id) { if (newVal && newVal.id && this.touchedView.id !== newVal.id) {
// 带 id 的动作给撤回时使用,不带 id表示对当前选中对象进行操作 // 带 id 的动作给撤回时使用,不带 id表示对当前选中对象进行操作
const { const {
@ -118,17 +119,21 @@ Component({
const draw = { const draw = {
width: this.currentPalette.width, width: this.currentPalette.width,
height: this.currentPalette.height, height: this.currentPalette.height,
views: [doView] views: this.isEmpty(doView) ? [] : [doView]
} }
const pen = new Pen(this.globalContext, draw); const pen = new Pen(this.globalContext, draw);
pen.paint(); if (isMoving && this.currentPalette.views[0].type === 'text') {
pen.paint(callback, true, this.movingCache);
} else {
pen.paint(callback)
}
const { const {
rect rect
} = doView } = doView
const block = { const block = {
width: this.currentPalette.width, width: this.currentPalette.width,
height: this.currentPalette.height, height: this.currentPalette.height,
views: [{ views: this.isEmpty(this.touchedView) ? [] : [{
type: 'rect', type: 'rect',
css: { css: {
height: `${rect.bottom - rect.top}px`, height: `${rect.bottom - rect.top}px`,
@ -151,6 +156,19 @@ Component({
} }
}] }]
} }
if (this.touchedView.type === 'text') {
block.views.push({
type: 'rect',
css: {
height: `${2 * ACTION_POINT_RADIUS}px`,
width: `${2 * ACTION_POINT_RADIUS}px`,
borderRadius: `${ACTION_POINT_RADIUS}px`,
color: '#0000ff',
left: `${rect.left - ACTION_POINT_RADIUS}px`,
top: `${rect.top - ACTION_POINT_RADIUS}px`
}
})
}
const topBlock = new Pen(this.frontContext, block) const topBlock = new Pen(this.frontContext, block)
topBlock.paint(); topBlock.paint();
}, },
@ -173,34 +191,48 @@ Component({
) )
}, },
isDelete(x, y, rect) {
return (x > rect.left - ACTION_POINT_RADIUS &&
y > rect.top - ACTION_POINT_RADIUS &&
x < rect.left + ACTION_POINT_RADIUS &&
y < rect.top + ACTION_POINT_RADIUS)
},
touchedView: {}, touchedView: {},
findedIndex: -1, findedIndex: -1,
onClick(event) { onClick() {
const x = this.startX const x = this.startX
const y = this.startY const y = this.startY
const totalLayerCount = this.currentPalette.views.length const totalLayerCount = this.currentPalette.views.length
const hasTouchedView = this.findedIndex !== -1
this.touchedView = {}
let canBeTouched = [] let canBeTouched = []
let isDelete = false
for (let i = totalLayerCount - 1; i >= 0; i--) { for (let i = totalLayerCount - 1; i >= 0; i--) {
const view = this.currentPalette.views[i] const view = this.currentPalette.views[i]
const { const {
rect rect
} = view } = view
if (this.inArea(x, y, rect, hasTouchedView)) { if (this.touchedView && this.touchedView.id &&
this.touchedView.id === view.id &&
this.isDelete(x, y, rect)) {
canBeTouched.length = 0
this.currentPalette.views.splice(i, 1)
isDelete = true
break
}
if (this.inArea(x, y, rect, !this.isEmpty(this.touchedView))) {
canBeTouched.push({ canBeTouched.push({
view, view,
index: i index: i
}) })
} }
} }
this.touchedView = {}
if (canBeTouched.length === 0) { if (canBeTouched.length === 0) {
this.findedIndex = -1 this.findedIndex = -1
} else { } else {
let i = 0 let i = 0
const touchAble = canBeTouched.filter(item => Boolean(item.view.id)) const touchAble = canBeTouched.filter(item => Boolean(item.view.id))
if (touchAble.length === 0) { if (touchAble.length === 0) {
this.touchedView = {}
this.findedIndex = canBeTouched[0].index this.findedIndex = canBeTouched[0].index
} else { } else {
for (i = 0; i < touchAble.length; i++) { for (i = 0; i < touchAble.length; i++) {
@ -226,7 +258,6 @@ Component({
} }
if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) { if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) {
// 证明点击了背景 或无法移动的view // 证明点击了背景 或无法移动的view
this.touchedView = {}
const block = { const block = {
width: this.currentPalette.width, width: this.currentPalette.width,
height: this.currentPalette.height, height: this.currentPalette.height,
@ -234,7 +265,9 @@ Component({
} }
const topBlock = new Pen(this.frontContext, block) const topBlock = new Pen(this.frontContext, block)
topBlock.paint(); topBlock.paint();
if (this.findedIndex < 0) { if (isDelete) {
this.doAction()
} else if (this.findedIndex < 0) {
this.triggerEvent('touchStart', {}) this.triggerEvent('touchStart', {})
} }
this.findedIndex = -1 this.findedIndex = -1
@ -260,11 +293,15 @@ Component({
} }
if (this.prevFindedIndex < this.findedIndex) { if (this.prevFindedIndex < this.findedIndex) {
new Pen(this.bottomContext, bottomDraw).paint(); new Pen(this.bottomContext, bottomDraw).paint();
this.doAction() this.doAction(null, (callbackInfo) => {
this.movingCache = callbackInfo
})
new Pen(this.topContext, topDraw).paint(); new Pen(this.topContext, topDraw).paint();
} else { } else {
new Pen(this.topContext, topDraw).paint(); new Pen(this.topContext, topDraw).paint();
this.doAction() this.doAction(null, (callbackInfo) => {
this.movingCache = callbackInfo
})
new Pen(this.bottomContext, bottomDraw).paint(); new Pen(this.bottomContext, bottomDraw).paint();
} }
this.prevFindedIndex = this.findedIndex this.prevFindedIndex = this.findedIndex
@ -290,6 +327,7 @@ Component({
} = this.touchedView } = this.touchedView
if (rect.right - ACTION_POINT_RADIUS < x && x < rect.right + ACTION_POINT_RADIUS && rect.bottom - ACTION_POINT_RADIUS < y && y < rect.bottom + ACTION_POINT_RADIUS) { if (rect.right - ACTION_POINT_RADIUS < x && x < rect.right + ACTION_POINT_RADIUS && rect.bottom - ACTION_POINT_RADIUS < y && y < rect.bottom + ACTION_POINT_RADIUS) {
this.isScale = true this.isScale = true
this.movingCache = {}
this.startH = rect.bottom - rect.top this.startH = rect.bottom - rect.top
this.startW = rect.right - rect.left this.startW = rect.right - rect.left
} else { } else {
@ -357,7 +395,11 @@ Component({
} }
this.doAction({ this.doAction({
css css
}) }, (callbackInfo) => {
if (this.isScale) {
this.movingCache = callbackInfo
}
}, !this.isScale)
}, },
initScreenK() { initScreenK() {
@ -378,12 +420,6 @@ Component({
initDancePalette() { initDancePalette() {
this.initScreenK(); this.initScreenK();
this.hasIdViews = []
this.properties.dancePalette && this.properties.dancePalette.views.map(view => {
if (view.id) {
this.hasIdViews.push(view)
}
})
this.downloadImages(this.properties.dancePalette).then((palette) => { this.downloadImages(this.properties.dancePalette).then((palette) => {
this.currentPalette = palette this.currentPalette = palette
@ -507,10 +543,10 @@ Component({
setTimeout(() => { setTimeout(() => {
wx.canvasToTempFilePath({ wx.canvasToTempFilePath({
canvasId: 'photo', canvasId: 'photo',
success: function (res) { success: function(res) {
that.getImageInfo(res.tempFilePath); that.getImageInfo(res.tempFilePath);
}, },
fail: function (error) { fail: function(error) {
console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`); console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`);
that.triggerEvent('imgErr', { that.triggerEvent('imgErr', {
error: error error: error
@ -584,4 +620,4 @@ function setStringPrototype(screenK, scale) {
} }
return res; return res;
}; };
} }