diff --git a/CHANGELOG.md b/CHANGELOG.md
index 611a807d..efa29ba2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
-#### 2024.3.12
+#### 2024.3.14
1. `U` 文字支持textStrokeWidth和textStrokeColor实现描边功能;
-2. `U` 小游戏插件发布1.0.8版本;
+2. `U` 文字支持textShadow实现阴影功能;
+3. `U` 小游戏插件发布1.0.8版本;
#### 2023.1.03
1. `U` 小游戏插件发布1.0.7版本;
diff --git a/demos/noengine/engine.js b/demos/noengine/engine.js
index 75a86537..2e211811 100644
--- a/demos/noengine/engine.js
+++ b/demos/noengine/engine.js
@@ -631,6 +631,9 @@ var repaintAffectedStyles = [
'borderColor',
'opacity',
'transform',
+ 'textStrokeColor',
+ 'textStrokeWidth',
+ 'textShadow',
];
var allStyles = reflowAffectedStyles.concat(repaintAffectedStyles);
@@ -957,6 +960,7 @@ module.exports.TinyEmitter = E;
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ backgroundImageParser: () => (/* binding */ backgroundImageParser),
+/* harmony export */ isValidTextShadow: () => (/* binding */ isValidTextShadow),
/* harmony export */ rotateParser: () => (/* binding */ rotateParser)
/* harmony export */ });
function degreesToRadians(degrees) {
@@ -986,6 +990,10 @@ function backgroundImageParser(val) {
console.error("[Layout]: ".concat(val, " is not a valid backgroundImage"));
return null;
}
+var textShadowReg = /^(\d+px\s){2}\d+px\s[a-zA-Z]+(,\s*(\d+px\s){2}\d+px\s[a-zA-Z]+)*$/;
+function isValidTextShadow(textShadow) {
+ return textShadowReg.test(textShadow);
+}
/***/ }),
@@ -3708,8 +3716,8 @@ function parseText(style, value) {
var Text = /** @class */ (function (_super) {
__extends(Text, _super);
function Text(_a) {
- var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var _this = this;
+ var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var originStyleWidth = style.width;
// 没有设置宽度的时候通过canvas计算出文字宽度
if (originStyleWidth === undefined) {
@@ -3736,8 +3744,31 @@ var Text = /** @class */ (function (_super) {
_this.ctx = null;
_this.valuesrc = value;
_this.originStyleWidth = originStyleWidth;
+ if (style.textShadow) {
+ _this.parseTextShadow(style.textShadow);
+ }
return _this;
}
+ Text.prototype.styleChangeHandler = function (prop, val) {
+ if (prop === 'textShadow') {
+ this.parseTextShadow(val);
+ }
+ };
+ Text.prototype.parseTextShadow = function (textShadow) {
+ // if (!isValidTextShadow(textShadow)) {
+ // console.error(`[Layout]: ${textShadow} is not a valid textShadow`);
+ // } else {
+ // 解析 text-shadow 字符串
+ this.textShadows = textShadow.split(',').map(function (shadow) {
+ var parts = shadow.trim().split(/\s+/);
+ var offsetX = parseFloat(parts[0]);
+ var offsetY = parseFloat(parts[1]);
+ var blurRadius = parseFloat(parts[2]);
+ var color = parts[3];
+ return { offsetX: offsetX, offsetY: offsetY, blurRadius: blurRadius, color: color };
+ });
+ // }
+ };
Object.defineProperty(Text.prototype, "value", {
get: function () {
return this.valuesrc;
@@ -3785,6 +3816,7 @@ var Text = /** @class */ (function (_super) {
}
};
Text.prototype.render = function () {
+ var _this = this;
var style = this.style;
var ctx = this.ctx;
ctx.save();
@@ -3806,7 +3838,26 @@ var Text = /** @class */ (function (_super) {
ctx.textBaseline = 'middle';
drawY += style.lineHeight / 2;
}
- ctx.fillText(this.value, drawX - originX, drawY - originY);
+ // 纹理文字描边
+ if (style.textStrokeColor) {
+ ctx.lineWidth = style.textStrokeWidth || 1;
+ ctx.strokeStyle = style.textStrokeColor;
+ ctx.strokeText(this.value, drawX - originX, drawY - originY);
+ }
+ // 处理文字阴影
+ if (this.textShadows) {
+ this.textShadows.forEach(function (_a) {
+ var offsetX = _a.offsetX, offsetY = _a.offsetY, blurRadius = _a.blurRadius, color = _a.color;
+ ctx.shadowOffsetX = offsetX;
+ ctx.shadowOffsetY = offsetY;
+ ctx.shadowBlur = blurRadius;
+ ctx.shadowColor = color;
+ ctx.fillText(_this.value, drawX - originX, drawY - originY);
+ });
+ }
+ else {
+ ctx.fillText(this.value, drawX - originX, drawY - originY);
+ }
ctx.translate(-originX, -originY);
ctx.restore();
};
@@ -5338,8 +5389,8 @@ function checkNeedHideScrollBar(direction, dimensions) {
var ScrollBar = /** @class */ (function (_super) {
__extends(ScrollBar, _super);
function ScrollBar(_a) {
- var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var _this = this;
+ var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var style = Object.assign({
backgroundColor: backgroundColor,
position: 'absolute',
@@ -5911,7 +5962,7 @@ var Layout = /** @class */ (function (_super) {
/**
* 当前 Layout 版本,一般跟小游戏插件版本对齐
*/
- _this.version = '1.0.7';
+ _this.version = '1.0.8';
_this.env = _env__WEBPACK_IMPORTED_MODULE_0__["default"];
/**
* Layout 渲染的目标画布对应的 2d context
diff --git a/demos/noengine/game.js b/demos/noengine/game.js
index 1defa6a3..bdc6140e 100644
--- a/demos/noengine/game.js
+++ b/demos/noengine/game.js
@@ -73,6 +73,7 @@ let style = {
fontSize: 50,
textAlign: 'center',
marginTop: 20,
+ // textShadow: '2px 2px 2px red',
},
rank: {
diff --git a/demos/noengine/sub/engine.js b/demos/noengine/sub/engine.js
index 75a86537..2e211811 100644
--- a/demos/noengine/sub/engine.js
+++ b/demos/noengine/sub/engine.js
@@ -631,6 +631,9 @@ var repaintAffectedStyles = [
'borderColor',
'opacity',
'transform',
+ 'textStrokeColor',
+ 'textStrokeWidth',
+ 'textShadow',
];
var allStyles = reflowAffectedStyles.concat(repaintAffectedStyles);
@@ -957,6 +960,7 @@ module.exports.TinyEmitter = E;
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ backgroundImageParser: () => (/* binding */ backgroundImageParser),
+/* harmony export */ isValidTextShadow: () => (/* binding */ isValidTextShadow),
/* harmony export */ rotateParser: () => (/* binding */ rotateParser)
/* harmony export */ });
function degreesToRadians(degrees) {
@@ -986,6 +990,10 @@ function backgroundImageParser(val) {
console.error("[Layout]: ".concat(val, " is not a valid backgroundImage"));
return null;
}
+var textShadowReg = /^(\d+px\s){2}\d+px\s[a-zA-Z]+(,\s*(\d+px\s){2}\d+px\s[a-zA-Z]+)*$/;
+function isValidTextShadow(textShadow) {
+ return textShadowReg.test(textShadow);
+}
/***/ }),
@@ -3708,8 +3716,8 @@ function parseText(style, value) {
var Text = /** @class */ (function (_super) {
__extends(Text, _super);
function Text(_a) {
- var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var _this = this;
+ var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var originStyleWidth = style.width;
// 没有设置宽度的时候通过canvas计算出文字宽度
if (originStyleWidth === undefined) {
@@ -3736,8 +3744,31 @@ var Text = /** @class */ (function (_super) {
_this.ctx = null;
_this.valuesrc = value;
_this.originStyleWidth = originStyleWidth;
+ if (style.textShadow) {
+ _this.parseTextShadow(style.textShadow);
+ }
return _this;
}
+ Text.prototype.styleChangeHandler = function (prop, val) {
+ if (prop === 'textShadow') {
+ this.parseTextShadow(val);
+ }
+ };
+ Text.prototype.parseTextShadow = function (textShadow) {
+ // if (!isValidTextShadow(textShadow)) {
+ // console.error(`[Layout]: ${textShadow} is not a valid textShadow`);
+ // } else {
+ // 解析 text-shadow 字符串
+ this.textShadows = textShadow.split(',').map(function (shadow) {
+ var parts = shadow.trim().split(/\s+/);
+ var offsetX = parseFloat(parts[0]);
+ var offsetY = parseFloat(parts[1]);
+ var blurRadius = parseFloat(parts[2]);
+ var color = parts[3];
+ return { offsetX: offsetX, offsetY: offsetY, blurRadius: blurRadius, color: color };
+ });
+ // }
+ };
Object.defineProperty(Text.prototype, "value", {
get: function () {
return this.valuesrc;
@@ -3785,6 +3816,7 @@ var Text = /** @class */ (function (_super) {
}
};
Text.prototype.render = function () {
+ var _this = this;
var style = this.style;
var ctx = this.ctx;
ctx.save();
@@ -3806,7 +3838,26 @@ var Text = /** @class */ (function (_super) {
ctx.textBaseline = 'middle';
drawY += style.lineHeight / 2;
}
- ctx.fillText(this.value, drawX - originX, drawY - originY);
+ // 纹理文字描边
+ if (style.textStrokeColor) {
+ ctx.lineWidth = style.textStrokeWidth || 1;
+ ctx.strokeStyle = style.textStrokeColor;
+ ctx.strokeText(this.value, drawX - originX, drawY - originY);
+ }
+ // 处理文字阴影
+ if (this.textShadows) {
+ this.textShadows.forEach(function (_a) {
+ var offsetX = _a.offsetX, offsetY = _a.offsetY, blurRadius = _a.blurRadius, color = _a.color;
+ ctx.shadowOffsetX = offsetX;
+ ctx.shadowOffsetY = offsetY;
+ ctx.shadowBlur = blurRadius;
+ ctx.shadowColor = color;
+ ctx.fillText(_this.value, drawX - originX, drawY - originY);
+ });
+ }
+ else {
+ ctx.fillText(this.value, drawX - originX, drawY - originY);
+ }
ctx.translate(-originX, -originY);
ctx.restore();
};
@@ -5338,8 +5389,8 @@ function checkNeedHideScrollBar(direction, dimensions) {
var ScrollBar = /** @class */ (function (_super) {
__extends(ScrollBar, _super);
function ScrollBar(_a) {
- var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var _this = this;
+ var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var style = Object.assign({
backgroundColor: backgroundColor,
position: 'absolute',
@@ -5911,7 +5962,7 @@ var Layout = /** @class */ (function (_super) {
/**
* 当前 Layout 版本,一般跟小游戏插件版本对齐
*/
- _this.version = '1.0.7';
+ _this.version = '1.0.8';
_this.env = _env__WEBPACK_IMPORTED_MODULE_0__["default"];
/**
* Layout 渲染的目标画布对应的 2d context
diff --git a/demos/noengine/sub/render/style.js b/demos/noengine/sub/render/style.js
index 1cae90cc..3ad05762 100644
--- a/demos/noengine/sub/render/style.js
+++ b/demos/noengine/sub/render/style.js
@@ -17,14 +17,19 @@ export default {
},
title: {
+ fontFamily: '猫啃什锦黑',
width: 144,
- fontSize: 48,
+ fontSize: 60,
height: 120,
lineHeight: 120,
textAlign: "center",
fontWeight: "bold",
borderBottomWidth: 6,
borderColor: "#000000",
+ color: '#ffffff',
+ textStrokeWidth: 2,
+ textStrokeColor: '#000000',
+ textShadow: '1px 1px 2px #0000ff',
},
rankList: {
@@ -91,6 +96,7 @@ export default {
width: 350,
marginLeft: 30,
fontFamily: '"Lucida Console", "Courier New", monospace',
+ textShadow: '2px 2px 2px red',
},
listScoreUnit: {
diff --git a/demos/noengine/sub/render/template.js b/demos/noengine/sub/render/template.js
index 6a24b39e..1bb3f3e3 100644
--- a/demos/noengine/sub/render/template.js
+++ b/demos/noengine/sub/render/template.js
@@ -1,7 +1,7 @@
const template = `
diff --git a/dist/index.d.ts b/dist/index.d.ts
index da039c44..9c696b3b 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -79,6 +79,15 @@ interface IStyle {
fontWeight?: string;
fontFamily?: string;
transform?: string;
+ textStrokeWidth?: number;
+ textStrokeColor?: string;
+ /**
+ * 文字阴影效果,textShadow的格式并不是严格的CSS格式,仅支持两种格式
+ * textShadow: 1px 1px 2px pink
+ * textShadow: 1px 1px 2px red, 0 0 1px blue, 0 0 1px blue, 1px 1px 2px red
+ * 也就是支持任意数量的阴影效果,每个阴影效果由四个值指定,分别是 shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor
+ */
+ textShadow?: string;
}
declare class Rect {
@@ -381,6 +390,12 @@ declare class Image extends Element {
interface ITextProps extends IElementOptions {
value?: string;
}
+interface ITextShadow {
+ offsetX: number;
+ offsetY: number;
+ blurRadius: number;
+ color: string;
+}
declare class Text extends Element {
private valuesrc;
private originStyleWidth;
@@ -389,7 +404,10 @@ declare class Text extends Element {
font: string;
textAlign: CanvasTextAlign;
fillStyle: string;
+ textShadows: null | ITextShadow[];
constructor({ style, idName, className, value, dataset, }: ITextProps);
+ styleChangeHandler(prop: string, val: any): void;
+ private parseTextShadow;
get value(): string;
set value(newValue: string);
toCanvasData(): void;
diff --git a/dist/index.js b/dist/index.js
index 75a86537..2e211811 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -631,6 +631,9 @@ var repaintAffectedStyles = [
'borderColor',
'opacity',
'transform',
+ 'textStrokeColor',
+ 'textStrokeWidth',
+ 'textShadow',
];
var allStyles = reflowAffectedStyles.concat(repaintAffectedStyles);
@@ -957,6 +960,7 @@ module.exports.TinyEmitter = E;
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ backgroundImageParser: () => (/* binding */ backgroundImageParser),
+/* harmony export */ isValidTextShadow: () => (/* binding */ isValidTextShadow),
/* harmony export */ rotateParser: () => (/* binding */ rotateParser)
/* harmony export */ });
function degreesToRadians(degrees) {
@@ -986,6 +990,10 @@ function backgroundImageParser(val) {
console.error("[Layout]: ".concat(val, " is not a valid backgroundImage"));
return null;
}
+var textShadowReg = /^(\d+px\s){2}\d+px\s[a-zA-Z]+(,\s*(\d+px\s){2}\d+px\s[a-zA-Z]+)*$/;
+function isValidTextShadow(textShadow) {
+ return textShadowReg.test(textShadow);
+}
/***/ }),
@@ -3708,8 +3716,8 @@ function parseText(style, value) {
var Text = /** @class */ (function (_super) {
__extends(Text, _super);
function Text(_a) {
- var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var _this = this;
+ var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var originStyleWidth = style.width;
// 没有设置宽度的时候通过canvas计算出文字宽度
if (originStyleWidth === undefined) {
@@ -3736,8 +3744,31 @@ var Text = /** @class */ (function (_super) {
_this.ctx = null;
_this.valuesrc = value;
_this.originStyleWidth = originStyleWidth;
+ if (style.textShadow) {
+ _this.parseTextShadow(style.textShadow);
+ }
return _this;
}
+ Text.prototype.styleChangeHandler = function (prop, val) {
+ if (prop === 'textShadow') {
+ this.parseTextShadow(val);
+ }
+ };
+ Text.prototype.parseTextShadow = function (textShadow) {
+ // if (!isValidTextShadow(textShadow)) {
+ // console.error(`[Layout]: ${textShadow} is not a valid textShadow`);
+ // } else {
+ // 解析 text-shadow 字符串
+ this.textShadows = textShadow.split(',').map(function (shadow) {
+ var parts = shadow.trim().split(/\s+/);
+ var offsetX = parseFloat(parts[0]);
+ var offsetY = parseFloat(parts[1]);
+ var blurRadius = parseFloat(parts[2]);
+ var color = parts[3];
+ return { offsetX: offsetX, offsetY: offsetY, blurRadius: blurRadius, color: color };
+ });
+ // }
+ };
Object.defineProperty(Text.prototype, "value", {
get: function () {
return this.valuesrc;
@@ -3785,6 +3816,7 @@ var Text = /** @class */ (function (_super) {
}
};
Text.prototype.render = function () {
+ var _this = this;
var style = this.style;
var ctx = this.ctx;
ctx.save();
@@ -3806,7 +3838,26 @@ var Text = /** @class */ (function (_super) {
ctx.textBaseline = 'middle';
drawY += style.lineHeight / 2;
}
- ctx.fillText(this.value, drawX - originX, drawY - originY);
+ // 纹理文字描边
+ if (style.textStrokeColor) {
+ ctx.lineWidth = style.textStrokeWidth || 1;
+ ctx.strokeStyle = style.textStrokeColor;
+ ctx.strokeText(this.value, drawX - originX, drawY - originY);
+ }
+ // 处理文字阴影
+ if (this.textShadows) {
+ this.textShadows.forEach(function (_a) {
+ var offsetX = _a.offsetX, offsetY = _a.offsetY, blurRadius = _a.blurRadius, color = _a.color;
+ ctx.shadowOffsetX = offsetX;
+ ctx.shadowOffsetY = offsetY;
+ ctx.shadowBlur = blurRadius;
+ ctx.shadowColor = color;
+ ctx.fillText(_this.value, drawX - originX, drawY - originY);
+ });
+ }
+ else {
+ ctx.fillText(this.value, drawX - originX, drawY - originY);
+ }
ctx.translate(-originX, -originY);
ctx.restore();
};
@@ -5338,8 +5389,8 @@ function checkNeedHideScrollBar(direction, dimensions) {
var ScrollBar = /** @class */ (function (_super) {
__extends(ScrollBar, _super);
function ScrollBar(_a) {
- var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var _this = this;
+ var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var style = Object.assign({
backgroundColor: backgroundColor,
position: 'absolute',
@@ -5911,7 +5962,7 @@ var Layout = /** @class */ (function (_super) {
/**
* 当前 Layout 版本,一般跟小游戏插件版本对齐
*/
- _this.version = '1.0.7';
+ _this.version = '1.0.8';
_this.env = _env__WEBPACK_IMPORTED_MODULE_0__["default"];
/**
* Layout 渲染的目标画布对应的 2d context
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index d6766a58..ea06b839 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,6 +1,7 @@
-#### 2024.3.12
+#### 2024.3.14
1. `U` 文字支持textStrokeWidth和textStrokeColor实现描边功能;
-2. `U` 小游戏插件发布1.0.8版本;
+2. `U` 文字支持textShadow实现阴影功能;
+3. `U` 小游戏插件发布1.0.8版本;
#### 2023.1.03
1. `U` 小游戏插件发布1.0.7版本;
diff --git a/docs/components/overview.md b/docs/components/overview.md
index 0b39ef66..4150c437 100644
--- a/docs/components/overview.md
+++ b/docs/components/overview.md
@@ -62,8 +62,14 @@ Layout 通过 xml 组织布局,Layout 支持的标签列表如下。
| backgroundColor | string | | 背景的颜色,支持 6 位 16 进制、8 位 16 进制、rgb、rgba 四种格式的颜色 |
| textOverflow | ellipsis, clip | 默认为空,出于性能考虑,只有显式指定 textOverflow 属性的时候才会对文字进行截断处理 |
| letterSpacing | number | 默认值为 0,只对 bitmaptext 标签生效 |
-| textStrokeWidth | number | 文字描边的宽度,默认不描边 |
-| textStrokeColor | string | 描边的颜色,支持 6 位 16 进制、8 位 16 进制、rgb、rgba 四种格式的颜色, 如果指定了描边颜色但是没有指定描边宽度,描边宽度默认设置为1 |
+| textStrokeWidth **(v1.0.8开始支持)**| number | 文字描边的宽度,默认不描边 |
+| textStrokeColor **(v1.0.8开始支持)**| string | 描边的颜色,支持 6 位 16 进制、8 位 16 进制、rgb、rgba 四种格式的颜色, 如果指定了描边颜色但是没有指定描边宽度,描边宽度默认设置为1 |
+| textShadow **(v1.0.8开始支持)** | string | 文字阴影效果,textShadow的格式并不是严格的CSS格式,仅支持两种格式 如`textShadow: 1px 1px 2px pink`和`textShadow: 1px 1px 2px red, 0 0 1px blue, 0 0 1px blue, 1px 1px 2px red`,也就是支持任意数量的阴影效果,每个阴影效果由四个值指定,分别是 shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor |
+
+::: tip textShadow 特殊说明
+微信小游戏开放数据域暂不支持文字阴影效果,近期版本支持中。
+:::
+
### 容器
diff --git a/docs/overview/plugin.md b/docs/overview/plugin.md
index 0c10ced5..96779e2d 100644
--- a/docs/overview/plugin.md
+++ b/docs/overview/plugin.md
@@ -17,7 +17,7 @@
"openDataContext": "sub",
"plugins": {
"Layout": {
- "version": "1.0.7",
+ "version": "1.0.8",
"provider": "wx7a727ff7d940bb3f",
"contexts":[{"type":"openDataContext"}]
}
@@ -52,7 +52,7 @@ const Layout = requirePlugin('Layout').default;
"deviceOrientation": "portrait",
"plugins": {
"Layout": {
- "version": "1.0.7",
+ "version": "1.0.8",
"provider": "wx7a727ff7d940bb3f",
"contexts":[{"type":"gameContext"}]
}
@@ -75,7 +75,7 @@ const Layout = requirePlugin('Layout').default;
"deviceOrientation": "portrait",
"plugins": {
"Layout": {
- "version": "1.0.7",
+ "version": "1.0.8",
"provider": "wx7a727ff7d940bb3f",
"contexts":[{"type":"gameContext"}, {"type":"openDataContext"}]
}
@@ -86,6 +86,7 @@ const Layout = requirePlugin('Layout').default;
## 版本列表
| 版本 | 特性 |
| --------------- | ------------------- |
+| 1.0.8 | 支持文字描边和文字阴影效果,详情可见[布局和样式](../components//overview.md) |
| 1.0.7 | 修复1.0.6版本Image的borderRadius失效问题 |
| 1.0.6 | 1. 修复圆角矩形在有borderRadius的时候绘制不够圆润问题;2. 文字样式支持fontFamily属性;3. 修复 ScrollView 的滚动条在页面布局变化时会位置异常问题; |
| 1.0.5 | transform 部分属性支持,使用可见[教程](../tutorial/loading.md) |
diff --git a/package.json b/package.json
index ddbab03d..589bd0f6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "minigame-canvas-engine",
- "version": "1.0.16",
+ "version": "1.0.18",
"description": "1. 安装Node 2. 安装tnpm: 3. 安装webpack: npm install --save-dev webpack",
"main": "dist/index.js",
"scripts": {
diff --git a/packages/devtools/src/demos/helloworld.vue b/packages/devtools/src/demos/helloworld.vue
index 78297929..ffdedc78 100644
--- a/packages/devtools/src/demos/helloworld.vue
+++ b/packages/devtools/src/demos/helloworld.vue
@@ -3,7 +3,7 @@ import { defineComponent } from "vue";
let template = `
-
+
`;
@@ -22,7 +22,7 @@ let style = {
height: "100%",
lineHeight: 200,
fontSize: 40,
- textAlign: "center",
+ textAlign: "center",
},
// 文字的最终颜色为#ff0000
redText: {
@@ -53,6 +53,13 @@ export default defineComponent({
Layout.updateViewPort(canvas.getBoundingClientRect());
Layout.layout(context);
+
+ let testText = Layout.getElementById('testText');
+
+ // console.log(testText.style.textShadow)
+
+ // testText.style.textShadow = '2px 2px 2px blue'
+
},
},
});
diff --git a/packages/devtools/src/demos/ranklist.vue b/packages/devtools/src/demos/ranklist.vue
index a6c1a046..deb2f6a9 100644
--- a/packages/devtools/src/demos/ranklist.vue
+++ b/packages/devtools/src/demos/ranklist.vue
@@ -116,6 +116,7 @@ let style = {
lineHeight: 150,
width: 350,
marginLeft: 30,
+ textShadow: '1px 1px 2px #0000ff',
},
listScoreUnit: {
opacity: 0.5,
diff --git a/packages/devtools/src/demos/text.vue b/packages/devtools/src/demos/text.vue
index 0ed5f736..e10c8da6 100644
--- a/packages/devtools/src/demos/text.vue
+++ b/packages/devtools/src/demos/text.vue
@@ -2,77 +2,82 @@
import { defineComponent } from "vue";
import { template } from "dot";
let tpl = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
let style = {
container: {
- width: 600,
- height: 600,
- borderRadius: 12,
- backgroundColor: "#f3f3f3",
+ width: 800,
+ height: 1000,
+ backgroundColor: '#f3f3f3',
},
- item: {
- width: "100%",
- height: 150,
- justifyContent: "center",
- flexDirection: "row",
- alignItems: "center",
+ text: {
+ width: '100%',
+ height: 50,
+ fontSize: 24,
+ marginTop: 20,
+ },
+ textBox: {
+ width: '100%',
+ height: 50,
+ flexDirection: 'row',
+ alignItems: 'flex-end'
},
-
wegoing: {
- width: 100,
+ width: 50,
+ height:50,
+ },
+ lineHeightText: {
height: 100,
- borderRadius: 50,
+ lineHeight: 100,
},
- text: {
- fontSize: 36,
- // height: 36,
+ fontSizeText: {
+ fontSize: 36,
+ },
+ fontFamilyText: {
+ fontFamily: 'FangSong'
},
- testtext: {
- fontFamily: '"Lucida Console", "Courier New", monospace',
+ textAlignText: {
+ textAlign: 'right',
+ },
+ verticalAlignText: {
+ width: 460,
+ height: 24,
+ marginTop: 0,
+ verticalAlign: 'bottom',
+ },
+ colorText: {
+ color: 'red',
+ },
+ textOverflowText: {
+ textOverflow: 'ellipsis',
+ },
+ textStroke: {
+ textStrokeWidth: 1,
+ textStrokeColor: 'red',
+ },
+ textShadow: {
+ textShadow: '1px 1px 2px blue',
}
};
@@ -93,20 +98,14 @@ export default defineComponent({
let context = canvas.getContext("2d");
// 设置canvas的尺寸和样式的container比例一致
- canvas.style.width = 600 / 2 + "px";
- canvas.style.height = 600 / 2 + "px";
- canvas.width = 600;
- canvas.height = 600;
+ canvas.style.width = 800 / 2 + "px";
+ canvas.style.height = 1000 / 2 + "px";
+ canvas.width = 800;
+ canvas.height = 1000;
Layout.updateViewPort(canvas.getBoundingClientRect());
Layout.layout(context);
-
- const text = Layout.getElementsByClassName("testtext");
-
- text[3].on("click", () => {
- text[3].value = "测试文本加长" + Math.random();
- });
},
},
});
diff --git a/packages/plugin/minigame/sub/render/style.js b/packages/plugin/minigame/sub/render/style.js
index d765206c..a21388c6 100644
--- a/packages/plugin/minigame/sub/render/style.js
+++ b/packages/plugin/minigame/sub/render/style.js
@@ -1,116 +1,123 @@
export const style = {
- container: {
- width: 960,
- height: 1410,
- borderRadius: 12,
- },
+ container: {
+ width: 960,
+ height: 1410,
+ borderRadius: 12,
+ },
- header: {
- height: 120,
- width: 960,
- flexDirection: 'column',
- alignItems: 'center',
- backgroundColor: '#ffffff',
- borderBottomWidth: 0.5,
- borderColor: 'rgba(0, 0, 0, 0.3)',
- },
+ header: {
+ height: 120,
+ width: 960,
+ flexDirection: 'column',
+ alignItems: 'center',
+ backgroundColor: '#ffffff',
+ borderBottomWidth: 0.5,
+ borderColor: 'rgba(0, 0, 0, 0.3)',
+ },
- title: {
- width: 144,
- fontSize: 48,
- height: 120,
- lineHeight: 120,
- textAlign: 'center',
- fontWeight: 'bold',
- borderBottomWidth: 6,
- borderColor: '#000000',
- },
+ title: {
+ width: 144,
+ fontSize: 48,
+ height: 120,
+ lineHeight: 120,
+ textAlign: 'center',
+ fontWeight: 'bold',
+ borderBottomWidth: 6,
+ borderColor: '#000000',
+ textStrokeColor: 'red',
+ textStrokeWidth: 1,
+ // textShadow: '1px 1px 2px #f3f3f3',
+ },
- rankList: {
- width: 960,
- height: 1000,
- backgroundColor: '#ffffff',
- },
+ rankList: {
+ width: 960,
+ height: 1000,
+ backgroundColor: '#ffffff',
+ },
- list: {
- width : 960,
- height : 950,
- backgroundColor: '#ffffff',
- marginTop: 30,
- },
+ list: {
+ width: 960,
+ height: 950,
+ backgroundColor: '#ffffff',
+ marginTop: 30,
+ },
- listItem: {
- backgroundColor: '#F7F7F7',
- width: 960,
- height: 150,
- flexDirection: 'row',
- alignItems: 'center',
- },
+ listItem: {
+ backgroundColor: '#F7F7F7',
+ width: 960,
+ height: 150,
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
- listItemOld: {
- backgroundColor: '#ffffff',
- },
+ listItemOld: {
+ backgroundColor: '#ffffff',
+ },
- listItemNum: {
- fontSize: 32,
- fontWeight: 'bold',
- color: '#452E27',
- lineHeight: 150,
- height: 150,
- textAlign: 'center',
- width: 120,
- },
+ listItemNum: {
+ fontSize: 32,
+ fontWeight: 'bold',
+ color: '#452E27',
+ lineHeight: 150,
+ height: 150,
+ textAlign: 'center',
+ width: 120,
+ },
- listHeadImg: {
- borderRadius: 6,
- width: 90,
- height: 90,
- },
+ listHeadImg: {
+ borderRadius: 6,
+ width: 90,
+ height: 90,
+ },
- listItemScore: {
- fontSize: 48,
- fontWeight: 'bold',
- marginLeft : 10,
- height: 150,
- lineHeight: 150,
- width: 300,
- textAlign: 'right',
- },
+ listItemScore: {
+ fontSize: 48,
+ fontWeight: 'bold',
+ marginLeft: 10,
+ height: 150,
+ lineHeight: 150,
+ width: 300,
+ textAlign: 'right',
+ },
- listItemName:{
- fontSize: 36,
- height: 150,
- lineHeight: 150,
- width: 350,
- marginLeft: 30,
- },
+ listItemName: {
+ fontSize: 36,
+ height: 150,
+ lineHeight: 150,
+ width: 350,
+ marginLeft: 30,
+ textShadow: '1px 1px 2px blue',
+ },
- listScoreUnit: {
- opacity: 0.5,
- color: '#000000',
- fontSize: 30,
- height: 150,
- lineHeight: 150,
- marginLeft: 8,
- },
+ listScoreUnit: {
+ opacity: 0.5,
+ color: '#000000',
+ fontSize: 30,
+ height: 150,
+ lineHeight: 150,
+ marginLeft: 8,
+ },
- selfListItem: {
- borderRadius: 20,
- marginTop: 50,
- backgroundColor: '#ffffff',
- },
+ selfListItem: {
+ borderRadius: 20,
+ marginTop: 50,
+ backgroundColor: '#ffffff',
+ },
- listTips: {
- width: 960,
- height: 90,
- lineHeight: 90,
- textAlign: 'center',
- fontSize: 30,
- color: 'rgba(0,0,0,0.5)',
- backgroundColor: '#ffffff',
- borderRadius: 10,
- /*borderWidth: 1,
- borderColor: 'rgba(0, 0, 0, 0.3)',*/
- }
-}
+ listItemNameSelf: {
+ textShadow: '1px 1px 2px blue',
+ },
+ listTips: {
+ width: 960,
+ height: 90,
+ lineHeight: 90,
+ textAlign: 'center',
+ fontSize: 30,
+ color: 'rgba(0,0,0,0.5)',
+ backgroundColor: '#ffffff',
+ borderRadius: 10,
+ /*borderWidth: 1,
+ borderColor: 'rgba(0, 0, 0, 0.3)',*/
+ }
+}
diff --git a/packages/plugin/minigame/sub/render/template.js b/packages/plugin/minigame/sub/render/template.js
index be4f0ef4..4a03929d 100644
--- a/packages/plugin/minigame/sub/render/template.js
+++ b/packages/plugin/minigame/sub/render/template.js
@@ -25,7 +25,7 @@ const template = `
-
+
diff --git a/packages/plugin/minigame/sub/render/tplfn.js b/packages/plugin/minigame/sub/render/tplfn.js
index f44f2882..bcfd3966 100644
--- a/packages/plugin/minigame/sub/render/tplfn.js
+++ b/packages/plugin/minigame/sub/render/tplfn.js
@@ -23,6 +23,6 @@ export function tplFn(it) {
out += ' ';
}
}
- out += ' ';
+ out += ' ';
return out;
}
diff --git a/packages/plugin/plugin/engine.js b/packages/plugin/plugin/engine.js
index 75a86537..2e211811 100644
--- a/packages/plugin/plugin/engine.js
+++ b/packages/plugin/plugin/engine.js
@@ -631,6 +631,9 @@ var repaintAffectedStyles = [
'borderColor',
'opacity',
'transform',
+ 'textStrokeColor',
+ 'textStrokeWidth',
+ 'textShadow',
];
var allStyles = reflowAffectedStyles.concat(repaintAffectedStyles);
@@ -957,6 +960,7 @@ module.exports.TinyEmitter = E;
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ backgroundImageParser: () => (/* binding */ backgroundImageParser),
+/* harmony export */ isValidTextShadow: () => (/* binding */ isValidTextShadow),
/* harmony export */ rotateParser: () => (/* binding */ rotateParser)
/* harmony export */ });
function degreesToRadians(degrees) {
@@ -986,6 +990,10 @@ function backgroundImageParser(val) {
console.error("[Layout]: ".concat(val, " is not a valid backgroundImage"));
return null;
}
+var textShadowReg = /^(\d+px\s){2}\d+px\s[a-zA-Z]+(,\s*(\d+px\s){2}\d+px\s[a-zA-Z]+)*$/;
+function isValidTextShadow(textShadow) {
+ return textShadowReg.test(textShadow);
+}
/***/ }),
@@ -3708,8 +3716,8 @@ function parseText(style, value) {
var Text = /** @class */ (function (_super) {
__extends(Text, _super);
function Text(_a) {
- var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var _this = this;
+ var _b = _a.style, style = _b === void 0 ? {} : _b, _c = _a.idName, idName = _c === void 0 ? '' : _c, _d = _a.className, className = _d === void 0 ? '' : _d, _e = _a.value, value = _e === void 0 ? '' : _e, dataset = _a.dataset;
var originStyleWidth = style.width;
// 没有设置宽度的时候通过canvas计算出文字宽度
if (originStyleWidth === undefined) {
@@ -3736,8 +3744,31 @@ var Text = /** @class */ (function (_super) {
_this.ctx = null;
_this.valuesrc = value;
_this.originStyleWidth = originStyleWidth;
+ if (style.textShadow) {
+ _this.parseTextShadow(style.textShadow);
+ }
return _this;
}
+ Text.prototype.styleChangeHandler = function (prop, val) {
+ if (prop === 'textShadow') {
+ this.parseTextShadow(val);
+ }
+ };
+ Text.prototype.parseTextShadow = function (textShadow) {
+ // if (!isValidTextShadow(textShadow)) {
+ // console.error(`[Layout]: ${textShadow} is not a valid textShadow`);
+ // } else {
+ // 解析 text-shadow 字符串
+ this.textShadows = textShadow.split(',').map(function (shadow) {
+ var parts = shadow.trim().split(/\s+/);
+ var offsetX = parseFloat(parts[0]);
+ var offsetY = parseFloat(parts[1]);
+ var blurRadius = parseFloat(parts[2]);
+ var color = parts[3];
+ return { offsetX: offsetX, offsetY: offsetY, blurRadius: blurRadius, color: color };
+ });
+ // }
+ };
Object.defineProperty(Text.prototype, "value", {
get: function () {
return this.valuesrc;
@@ -3785,6 +3816,7 @@ var Text = /** @class */ (function (_super) {
}
};
Text.prototype.render = function () {
+ var _this = this;
var style = this.style;
var ctx = this.ctx;
ctx.save();
@@ -3806,7 +3838,26 @@ var Text = /** @class */ (function (_super) {
ctx.textBaseline = 'middle';
drawY += style.lineHeight / 2;
}
- ctx.fillText(this.value, drawX - originX, drawY - originY);
+ // 纹理文字描边
+ if (style.textStrokeColor) {
+ ctx.lineWidth = style.textStrokeWidth || 1;
+ ctx.strokeStyle = style.textStrokeColor;
+ ctx.strokeText(this.value, drawX - originX, drawY - originY);
+ }
+ // 处理文字阴影
+ if (this.textShadows) {
+ this.textShadows.forEach(function (_a) {
+ var offsetX = _a.offsetX, offsetY = _a.offsetY, blurRadius = _a.blurRadius, color = _a.color;
+ ctx.shadowOffsetX = offsetX;
+ ctx.shadowOffsetY = offsetY;
+ ctx.shadowBlur = blurRadius;
+ ctx.shadowColor = color;
+ ctx.fillText(_this.value, drawX - originX, drawY - originY);
+ });
+ }
+ else {
+ ctx.fillText(this.value, drawX - originX, drawY - originY);
+ }
ctx.translate(-originX, -originY);
ctx.restore();
};
@@ -5338,8 +5389,8 @@ function checkNeedHideScrollBar(direction, dimensions) {
var ScrollBar = /** @class */ (function (_super) {
__extends(ScrollBar, _super);
function ScrollBar(_a) {
- var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var _this = this;
+ var direction = _a.direction, dimensions = _a.dimensions, _b = _a.backgroundColor, backgroundColor = _b === void 0 ? 'rgba(162, 162, 162, 1)' : _b, _c = _a.width, width = _c === void 0 ? 16 : _c;
var style = Object.assign({
backgroundColor: backgroundColor,
position: 'absolute',
@@ -5911,7 +5962,7 @@ var Layout = /** @class */ (function (_super) {
/**
* 当前 Layout 版本,一般跟小游戏插件版本对齐
*/
- _this.version = '1.0.7';
+ _this.version = '1.0.8';
_this.env = _env__WEBPACK_IMPORTED_MODULE_0__["default"];
/**
* Layout 渲染的目标画布对应的 2d context
diff --git a/src/components/style.ts b/src/components/style.ts
index 8ca7549f..7a1cd828 100644
--- a/src/components/style.ts
+++ b/src/components/style.ts
@@ -32,6 +32,7 @@ const repaintAffectedStyles = [
'transform',
'textStrokeColor',
'textStrokeWidth',
+ 'textShadow',
];
const allStyles = reflowAffectedStyles.concat(repaintAffectedStyles);
@@ -106,6 +107,14 @@ interface IStyle {
textStrokeWidth?: number;
// 文字描边的颜色,如果指定了描边颜色但是没有指定描边宽度,描边宽度默认设置为1
textStrokeColor?: string;
+
+ /**
+ * 文字阴影效果,textShadow的格式并不是严格的CSS格式,仅支持两种格式
+ * textShadow: 1px 1px 2px pink
+ * textShadow: 1px 1px 2px red, 0 0 1px blue, 0 0 1px blue, 1px 1px 2px red
+ * 也就是支持任意数量的阴影效果,每个阴影效果由四个值指定,分别是 shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor
+ */
+ textShadow?: string;
}
export { repaintAffectedStyles, reflowAffectedStyles, allStyles, IStyle };
diff --git a/src/components/styleParser.ts b/src/components/styleParser.ts
index 2d52915e..0cabb90a 100644
--- a/src/components/styleParser.ts
+++ b/src/components/styleParser.ts
@@ -37,3 +37,8 @@ export function backgroundImageParser(val: string) {
return null;
}
+
+const textShadowReg = /^(\d+px\s){2}\d+px\s[a-zA-Z]+(,\s*(\d+px\s){2}\d+px\s[a-zA-Z]+)*$/;
+export function isValidTextShadow(textShadow: string) {
+ return textShadowReg.test(textShadow);
+}
\ No newline at end of file
diff --git a/src/components/text.ts b/src/components/text.ts
index 50c46d93..8cc3eaca 100644
--- a/src/components/text.ts
+++ b/src/components/text.ts
@@ -1,6 +1,7 @@
import Element from './elements';
import { IStyle } from './style';
import { IElementOptions } from './types';
+import { isValidTextShadow } from './styleParser';
import env from '../env'
const DEFAULT_FONT_FAMILY = 'sans-serif';
@@ -67,6 +68,13 @@ interface ITextProps extends IElementOptions {
value?: string;
}
+interface ITextShadow {
+ offsetX: number;
+ offsetY: number;
+ blurRadius: number;
+ color: string;
+}
+
export default class Text extends Element {
private valuesrc = '';
private originStyleWidth: number | string | undefined;
@@ -76,6 +84,8 @@ export default class Text extends Element {
public textAlign: CanvasTextAlign = 'left';
public fillStyle = '#000000';
+ public textShadows!: null | ITextShadow[];
+
constructor({
style = {},
idName = '',
@@ -105,6 +115,33 @@ export default class Text extends Element {
this.ctx = null;
this.valuesrc = value;
this.originStyleWidth = originStyleWidth;
+
+ if (style.textShadow) {
+ this.parseTextShadow(style.textShadow);
+ }
+ }
+
+ styleChangeHandler(prop: string, val: any) {
+ if (prop === 'textShadow') {
+ this.parseTextShadow(val);
+ }
+ }
+
+ private parseTextShadow(textShadow: string) {
+ // if (!isValidTextShadow(textShadow)) {
+ // console.error(`[Layout]: ${textShadow} is not a valid textShadow`);
+ // } else {
+ // 解析 text-shadow 字符串
+ this.textShadows = textShadow.split(',').map(shadow => {
+ const parts = shadow.trim().split(/\s+/);
+ const offsetX = parseFloat(parts[0]);
+ const offsetY = parseFloat(parts[1]);
+ const blurRadius = parseFloat(parts[2]);
+ const color = parts[3];
+
+ return { offsetX, offsetY, blurRadius, color };
+ });
+ // }
}
get value() {
@@ -188,12 +225,7 @@ export default class Text extends Element {
drawY += (style.lineHeight as number) / 2;
}
- ctx.fillText(
- this.value,
- drawX - originX,
- drawY - originY,
- );
-
+ // 纹理文字描边
if (style.textStrokeColor) {
ctx.lineWidth = style.textStrokeWidth || 1;
ctx.strokeStyle = style.textStrokeColor as string;
@@ -205,6 +237,27 @@ export default class Text extends Element {
);
}
+ // 处理文字阴影
+ if (this.textShadows) {
+ this.textShadows.forEach(({ offsetX, offsetY, blurRadius, color }) => {
+ ctx.shadowOffsetX = offsetX;
+ ctx.shadowOffsetY = offsetY;
+ ctx.shadowBlur = blurRadius;
+ ctx.shadowColor = color;
+ ctx.fillText(
+ this.value,
+ drawX - originX,
+ drawY - originY,
+ );
+ });
+ } else {
+ ctx.fillText(
+ this.value,
+ drawX - originX,
+ drawY - originY,
+ );
+ }
+
ctx.translate(-originX, -originY);
ctx.restore();
diff --git a/src/index.ts b/src/index.ts
index 964348ce..67b410b5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -66,7 +66,7 @@ class Layout extends Element {
/**
* 当前 Layout 版本,一般跟小游戏插件版本对齐
*/
- public version = '1.0.7';
+ public version = '1.0.8';
env = env;