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;