diff --git a/build/rollup.config.js b/build/rollup.config.js index f4762d3..bfe7079 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -9,6 +9,6 @@ export default { name: 'easyCanvas' }, plugins: [ - // terser() + terser() ] } diff --git a/dist/easy-canvas.min.js b/dist/easy-canvas.min.js index 02deab5..f3ff838 100644 --- a/dist/easy-canvas.min.js +++ b/dist/easy-canvas.min.js @@ -1,2030 +1 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.easyCanvas = factory()); -}(this, (function () { 'use strict'; - - const DISPLAY = { - BLOCK: 'block', - INLINE_BLOCK: 'inline-block', - INLINE: 'inline', // 用户不能设置inline,text默认为inline - FLEX: 'flex', - NONE: 'none' - }; - - const WIDTH = { - AUTO: 'auto', - OUTER: '100%' - }; - - const POSITION = { - ABSOLUTE: 'absolute', - FIXED: 'fixed', - RELATIVE: 'relative', - STATIC: 'static' - }; - - const TEXT_ALIGN = { - LEFT: 'left', - RIGHT: 'right', - CENTER: 'center' - }; - - const FLEX_DIRECTION = { - ROW: 'row', - COLUMN: 'column' - }; - - const DEFAULT_STYLES = { - display: DISPLAY.BLOCK, - fontSize: 14, - fontWeight: 400, - fontFamily: "Microsoft Yahei", - color: '#000', - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 0, - paddingRight: 0, - marginTop: 0, - marginBottom: 0, - marginLeft: 0, - marginRight: 0, - height: WIDTH.AUTO, - borderRadius: 0, - lineCap: 'square', - flexDirection: FLEX_DIRECTION.ROW, - verticalAlign: 'top', - textAlign:'left', - justifyContent: 'flex-start', - alignItems: 'flex-start', - whiteSpace: 'normal', - zIndex: 1 - }; - - var STYLES = { - DISPLAY, - WIDTH, - POSITION, - DEFAULT_STYLES, - TEXT_ALIGN, - FLEX_DIRECTION - }; - - function isExact(num) { - return typeof num === 'number' - } - - function isAuto(num) { - return num === 'auto' - } - - function isOuter(num) { - if (typeof num !== 'string') return - return num.match('%') - } - - function parseOuter(num) { - let _n = parseInt(num.replace('%', '')); - return (isNaN(_n) || _n < 0) ? 0 : (_n / 100) - } - - - function walk(element, callback) { - callback(element); - if (element.hasChildren()) { - element._getChildren().forEach(child => { - walk(child, callback); - }); - } - } - - - function isWX() { - return !window - } - - function isEndNode(el) { - return el.parent && !el.next && !el.hasChildren() - } - - class Line { - constructor() { - this.width = 0; - this.height = 0; - this.contentWidth = 0; // 右边界 - this.y = 0; // 上 - this.doorClosed = false; // 是否允许加入 - this.outerWidth = 0; - this.container = null; - this.elements = []; - this.start = null; // 起点,行最左边第一个 - this.end = null; // 结束 - this.offsetX = 0; - this.id = Math.random(); - } - - bind(el) { - this.container = el.parent; - this.height = el.parent && el.parent.renderStyles.lineHeight || 0; - this.outerWidth = el.parent && isAuto(el.parent.styles.width) ? Infinity : el.parent.renderStyles.contentWidth; - - this.start = el; - this.add(el); - } - - initLayout(el) { - this.right = el._getContainerLayout().contentX; - this.y = this.getPreLineBottom(el); - } - - refreshElementPosition(el) { - if (this.start === el) { - this.initLayout(el); - } - // 刷新位置,首先以左边计算 - el.x = this.right + this.offsetX; - el.y = this.y + (this.height - el.renderStyles.contentHeight) / 2; - this.right += el.renderStyles.width; - - } - - add(el) { - this.elements.push(el); - el.line = this; - this.refreshWidthHeight(el); - - if (!el.next) { - this.closeLine(); - } - } - - refreshWidthHeight(el) { - if (el.renderStyles.height > this.height) { - this.height = el.renderStyles.height; - } - - this.width += el.renderStyles.width; - } - - canIEnter(el) { - if ((el.renderStyles.width + this.width) > this.outerWidth) { - this.closeLine(); - return false - } else { - return true - } - } - - closeLine() { - // new line - this.end = this.elements[this.elements.length - 1]; - this.refreshXAlign(); - - } - - getPreLineBottom(el) { - if (el.pre) { - if (el.pre.line) { - return el.pre.line.height + el.pre.line.y - } else { - return el._getPreLayout().y + el._getPreLayout().height - } - } else { - return el._getContainerLayout().contentY - } - } - - refreshXAlign() { - if (this.outerWidth > 5000) return - if (!this.end.parent) return - let offsetX = this.outerWidth - this.width; - if (this.end.parent.renderStyles.textAlign === 'center') { - offsetX = offsetX / 2; - } else if (this.end.parent.renderStyles.textAlign === 'left') { - offsetX = 0; - } - this.offsetX = offsetX; - } - } - - /** - * Element类实现盒模型以及定位,不具备绘制 - * 其他类继承实现 - * - */ - - - - - class Element { - constructor(options, children) { - this.options = Object.assign({ attrs: {}, styles: {}, on: {} }, options); - this.children = children; - this.styles = null; - this.parent = null; - this.renderStyles = null; - this.x = 0; - this.y = 0; - this.pre = null; - this.next = null; - this.render = null; - this.root = null; - this.container = null; - // this.init() - } - - init() { - this._initStyles(); - this.initEvent(); - } - - initEvent() { - const { click } = this.options.on; - if (click) { - const { click } = this.options.on; - this.getLayer().eventManager.onClick(click, this); - } - } - - removeEvent() { - this.getLayer().eventManager.removeElement(this); - } - - getLayer() { - return this.root.layer - } - - mount(layer) { - layer.mountNode(this); - } - - _restore(callback) { - this.getCtx().save(); - callback(); - this.getCtx().restore(); - } - - _path(callback) { - this.getCtx().beginPath(); - callback(); - this.getCtx().closePath(); - } - - _initStyles() { - this.styles = Object.assign({}, this._getDefaultStyles(), this._getParentStyles(), this.options.styles || {}); - - this._completeStyles(); - - this._initRenderStyles(); - } - - _initRenderStyles() { - const renderStyles = { ...this.styles }; - const parentWidth = this._getContainerLayout().contentWidth; - const parentHeight = this._getContainerLayout().contentHeight; - - if (isAuto(renderStyles.width)) { - renderStyles.width = 0; - } else if (isOuter(renderStyles.width)) { - renderStyles.width = parseOuter(renderStyles.width) * parentWidth; - } - - if (isAuto(renderStyles.height)) { - renderStyles.height = 0; - } else if (isOuter(renderStyles.height)) { - renderStyles.height = parseOuter(renderStyles.height) * parentHeight; - } - - - - if (!renderStyles.width) renderStyles.width = 0; - if (!renderStyles.height) renderStyles.height = 0; - - - // 初始化contentWidth - renderStyles.contentWidth = renderStyles.width - renderStyles.paddingLeft - renderStyles.paddingRight - renderStyles.marginLeft - renderStyles.marginRight - renderStyles.borderWidth; - renderStyles.contentHeight = renderStyles.height - renderStyles.paddingTop - renderStyles.paddingBottom - renderStyles.marginTop - renderStyles.marginBottom - renderStyles.borderWidth; - this.renderStyles = renderStyles; - } - - /** - * 需要继承的styles放在这里 - */ - _getParentStyles() { - let { textAlign, lineHeight, fontSize, color, fontFamily, alignItems } = this.parent && this.parent.styles || {}; - let extendStyles = {}; - // if (textAlign) extendStyles.textAlign = textAlign - if (lineHeight) extendStyles.lineHeight = lineHeight; - if (fontSize) extendStyles.fontSize = fontSize; - if (color) extendStyles.color = color; - if (fontFamily) extendStyles.fontFamily = fontFamily; - return extendStyles - } - - _completeStyles() { - this._completeFlex(); - - this._completeWidth(); - - this._completeBorder(); - - this._completeFont(); - - } - - /** - * borderwidth到各个边 - */ - _completeBorder() { - let { borderWidth, borderLeftWidth, borderRightWidth, borderBottomWidth, borderTopWidth, borderRadius } = this.styles; - if (!borderWidth) { - this.styles.borderWidth = 0; - borderWidth = 0; - } - if (Array.isArray(borderWidth)) { - this.styles.borderTopWidth = borderWidth[0]; - this.styles.borderRightWidth = borderWidth[1]; - this.styles.borderBottomWidth = borderWidth[2]; - this.styles.borderLeftWidth = borderWidth[3]; - } else { - if (!borderLeftWidth) { - this.styles.borderLeftWidth = borderWidth; - } - if (!borderRightWidth) { - this.styles.borderRightWidth = borderWidth; - } - if (!borderBottomWidth) { - this.styles.borderBottomWidth = borderWidth; - } - if (!borderTopWidth) { - this.styles.borderTopWidth = borderWidth; - } - } - if (borderRadius) { - this.styles.overflow = 'hidden'; - } - } - - _completeWidth() { - if (!this.styles.width) { - if (this.styles.display === STYLES.DISPLAY.INLINE_BLOCK || this.styles.display === STYLES.DISPLAY.INLINE || !this.isInFlow()) { - this.styles.width = STYLES.WIDTH.AUTO; - } else if (this.styles.display === STYLES.DISPLAY.BLOCK || this.styles.display === STYLES.DISPLAY.FLEX) { - this.styles.width = STYLES.WIDTH.OUTER; - } else { - this.styles.width = 0; - } - } - } - - _completeFont() { - if (this.styles.fontSize && !this.styles.lineHeight) { - this.styles.lineHeight = this.styles.fontSize * 1.4; - } else if (!this.styles.lineHeight) { - this.styles.lineHeight = 14; - } - } - - _completeFlex() { - if (this.parent && this.parent.styles.display === STYLES.DISPLAY.FLEX) { - // flex布局内 width 和flex需要有一个 - if (!this.styles.width && !this.styles.flex) { - this.styles.flex = 1; - } - } - } - - _getDefaultStyles() { - return STYLES.DEFAULT_STYLES - } - - hasChildren() { - return Array.isArray(this.children) && this.children.length ? true : false - } - - _getChildren() { - return this.hasChildren() ? this.children : [] - } - - // 获取文档流中的子节点 - _getChildrenInFlow() { - return this._getChildren().filter(item => item.isInFlow()) - } - - // 是否在文档流中 - isInFlow() { - const { position, display } = this.styles; - return position !== STYLES.POSITION.ABSOLUTE && position !== STYLES.POSITION.FIXED - } - - _setParent(element) { - this.parent = element; - this.root = element.root; - } - - _setSibling(pre, next) { - this.pre = pre; - this.next = next; - } - - _generateRender() { - return this - } - - getCtx() { - return this.root.layer.ctx - } - - /** - * 实现文档流 需要知道上一个兄弟节点 - */ - _reflow() { - - - } - - // paint队列执行 - _repaint() { - this.getCtx().save(); - - this._drawBox(); - - this._drawBackground(); - - this._drawContent(); - - } - - // 栈 - _afterPaint() { - // 这里通过this.ctx栈实现了overflow - // 第一步判断没有子元素,绘制完成即restore 有子元素需要子元素全部绘制完毕再restore - if (!this.hasChildren()) { - this.getCtx().restore(); - } - - // 如果到了层级的最后一个 释放父级的stack - if (isEndNode(this)) { - // 首先释放第一层父级 - this.getCtx().restore(); - let cur = this.parent; - while (cur && !cur.next) { - // 如果父级也是同级最后一个,再闭合上一个 - this.getCtx().restore(); - cur = cur.parent; - } - } - } - - _drawBox() { - - } - - _drawContent() { - - } - - _drawBackground() { - - } - - _initWidthHeight() { - const { width, height, display, flex, marginLeft, marginRight, marginTop, marginBottom } = this.styles; - const layout = this._measureLayout(); - // 初始化宽度高度 - if (isAuto(width)) { - this.renderStyles.contentWidth = layout.width; - } - - if (isAuto(height)) { - // 不填就是auto - this.renderStyles.contentHeight = layout.height; - } - - this._refreshLayoutWithContent(); - - if (display === STYLES.DISPLAY.INLINE_BLOCK) { - // 如果是inline-block 这里仅计算高度 - this._bindLine(); - } - } - - _initPosition() { - const { contentX, contentY, contentWidth, contentHeight } = this._getContainerLayout(); - const { paddingLeft, paddingTop, borderWidth, marginLeft, marginTop } = this.renderStyles; - // 初始化ctx位置 - if (!this.isInFlow()) { - // 不在文档流中 - const { top, bottom, right, left, width, height } = this.renderStyles; - if (isExact(top)) { - this.y = contentY + top; - } else if (isExact(bottom)) { - this.y = contentY + contentHeight - bottom - height; - } - - if (isExact(left)) { - this.x = contentX + left; - } else if (isExact(right)) { - this.x = contentX + contentWidth - right - width; - } - } else if (this._InFlexBox()) ; else if (this.renderStyles.display === STYLES.DISPLAY.INLINE_BLOCK) { - // inline-block到line里计算 - // this._bindLine() - this.line.refreshElementPosition(this); - } else { - this.x = contentX; - this.y = this._getPreLayout().y + this._getPreLayout().height; - } - this.contentX = this.x + paddingLeft + borderWidth + marginLeft; - this.contentY = this.y + paddingTop + borderWidth + marginTop; - } - - _InFlexBox() { - if (!this.parent) return false - if (this.parent && this.parent.renderStyles.display === STYLES.DISPLAY.FLEX) return true - } - - - // 父元素根据子元素撑开content后,再计算width - _refreshLayoutWithContent() { - this.renderStyles.height = this.renderStyles.contentHeight + this.renderStyles.paddingTop + this.renderStyles.paddingBottom + this.renderStyles.marginTop + this.renderStyles.marginBottom + this.renderStyles.borderWidth; - this.renderStyles.width = this.renderStyles.contentWidth + this.renderStyles.paddingLeft + this.renderStyles.paddingRight + this.renderStyles.marginLeft + this.renderStyles.marginRight + this.renderStyles.borderWidth; - } - - _bindLine() { - if (this.pre && this.pre.line && this.pre.line.canIEnter(this)) { - this.pre.line.add(this); - } else { - // 新行 - new Line().bind(this); - } - } - - /** - * 计算子元素高度,撑开父元素 - */ - _calcContentHeightWidthChildren() { - // todo 没有考虑inline-block - let complete = true; - let lineHeight = 0; - const heightArr = []; - this._getChildrenInFlow().forEach(child => { - // 如果还有没计算完成的,这里可以去掉了,全面通过是否有下一个元素判断好了 - if (!typeof child.renderStyles.height === 'number') { - complete = false; - } - // 如果是第一个元素需要纳入计算 - if (child._needNewLine()) { - // 从一行到下一个新一行 - heightArr.push(lineHeight); - lineHeight = child.renderStyles.height; - - } else { - // 如果是同一行,取最大的 - if (child.renderStyles.height > lineHeight) { - lineHeight = child.renderStyles.height; - } - } - - if (!child.next) { - // 如果没有下一个了 - heightArr.push(lineHeight); - lineHeight = 0; - } - }); - - return complete ? heightArr.reduce((sum, val) => sum + (val >= 0 ? val : 0)) : this.renderStyles.height - - } - - /** - * 是否需要新起一行 - */ - _needNewLine() { - const { display } = this.renderStyles; - const { whiteSpace } = this.parent && this.parent.renderStyles || {}; - if (!this.pre) return true - // flex容器内 - if (this.parent && this.parent.renderStyles.display === STYLES.DISPLAY.FLEX && this.pre && this.parent.renderStyles.flexDirection === STYLES.FLEX_DIRECTION.ROW) { - return false - } - - // block等 - if (display === STYLES.DISPLAY.BLOCK || display === STYLES.DISPLAY.FLEX) { - return true - } - - // 到这里都是inline-block或者inline了 - if (whiteSpace === 'nowrap') return false - if (this.pre) { - let { width } = this.renderStyles; - if (width === STYLES.WIDTH.AUTO) width = 0; - const { display, width: preWidth } = this.pre.renderStyles; - const { width: containerWidth, x: containerX } = this._getContainerLayout(); - if (display === STYLES.DISPLAY.BLOCK || display === STYLES.DISPLAY.FLEX) { - return true - } else if ((preWidth + this.pre.x + width) > (containerX + containerWidth)) { - // 这里将当前宽度等于上一个的宽度了 因为这里宽度还是0,暂时还没有好的解决方案 - // 如果inlineblock顶到右边,换行 - return true - } - - } - - return false - - } - - _getContainerLayout() { - let container = this.parent; - if (this.styles.position === STYLES.POSITION.STATIC) ; - if (!container) { - // root - container = { - renderStyles: { - width: this.container.width, - height: this.container.height, - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 0, - paddingRight: 0, - marginLeft: 0, - marginRight: 0, - marginTop: 0, - marginBottom: 0, - contentWidth: this.container.width, - contentHeight: this.container.height - }, - x: 0, - y: 0, - contentX: 0, - contentY: 0 - }; - } - return { - width: container.renderStyles.width, - height: container.renderStyles.height, - x: container.x, - y: container.y, - paddingTop: container.renderStyles.paddingTop, - paddingBottom: container.renderStyles.paddingBottom, - paddingLeft: container.renderStyles.paddingLeft, - paddingRight: container.renderStyles.paddingRight, - marginLeft: container.renderStyles.marginLeft, - marginRight: container.renderStyles.marginRight, - marginTop: container.renderStyles.marginTop, - marginBottom: container.renderStyles.marginBottom, - contentX: container.contentX, - contentY: container.contentY, - contentWidth: container.renderStyles.contentWidth, - contentHeight: container.renderStyles.contentHeight - } - } - - // 这里前一个节点必须在文档流中 - _getPreLayout() { - let cur = this.pre; - while (cur && !cur.isInFlow()) { - cur = cur.pre; - } - // 如果没有前一个或者前面的都不在文档流中,获取容器的 - if (cur) { - return { - width: cur.renderStyles.width, - height: cur.renderStyles.height, - x: cur.x, - y: cur.y - } - } else { - return { - width: 0, - height: 0, - x: this._getContainerLayout().contentX, - y: this._getContainerLayout().contentY - } - } - } - - // 计算自身的高度 - _measureLayout() { - let width = 0; // 需要考虑原本的宽度 - let height = 0; - this._getChildrenInFlow().forEach(child => { - if (child.renderStyles.width > width) { - width = child.renderStyles.width; - } - height += child.renderStyles.height; - }); - - return { width, height } - } - - _px(num) { - // if (num && isExact(num)) { - // return num / this.root.container.dpr - // } - return num - } - - // 原理 统一从左边往右移动 必须在正常排列完成后再次进行 - _patchInlineBlockAlign() { - const { textAlign, contentWidth, lineHeight } = this.renderStyles; - const children = this._getChildrenInFlow(); - if (!children.length || children[0].renderStyles.display !== 'inline-block') return - let rightOffset = 0; - let curLineHeight = 0; - const translateX = (element) => { - let offsetY = 0; - if (element.renderStyles.verticalAlign === 'bottom') { - offsetY = curLineHeight - element.renderStyles.height; - } else if (element.renderStyles.verticalAlign === 'middle') { - offsetY = (curLineHeight - element.renderStyles.height) / 2; - } else { - offsetY = 0; - } - walk(element, (child) => { - if (child.isInFlow()) { - child.x += rightOffset; - child.contentX += rightOffset; - child.y += offsetY; - child.contentY += offsetY; - } - }); - - }; - const refreshX = (element) => { - curLineHeight = getLineHeight(element); - rightOffset = this.contentX + contentWidth - (element.x + element.renderStyles.width); - if (textAlign === 'center') rightOffset = rightOffset / 2; - if (textAlign === 'left' || !textAlign) rightOffset = 0; - }; - let cur = null; - for (let i = children.length - 1; i >= 0; i--) { - cur = children[i]; - if (!cur.next) { - // 最后一个 只有一个也会走这里 - refreshX(cur); - translateX(cur); - } else if (!cur.pre) { - // 第一个 - translateX(cur); - } else if (cur.next._needNewLine()) { - refreshX(cur); - translateX(cur); - } else { - translateX(cur); - } - } - } - - _patchFlex() { - const children = this._getChildrenInFlow(); - const { contentHeight, contentWidth, alignItems, justifyContent } = this.renderStyles; - if (this.renderStyles.display !== 'flex' || !children.length) return - let offsetX = 0; - let offsetY = 0; - if (justifyContent === 'flex-end') { - offsetX = (this.contentX + contentWidth) - (children[children.length - 1].renderStyles.width + children[children.length - 1].x); - } else if (justifyContent === 'center') { - offsetX = ((this.contentX + contentWidth) - (children[children.length - 1].renderStyles.width + children[children.length - 1].x)) / 2; - } - for (let i = children.length - 1; i >= 0; i--) { - if (alignItems === 'flex-end') { - offsetY = contentHeight - children[i].renderStyles.height; - } else if (alignItems === 'center') { - offsetY = (contentHeight - children[i].renderStyles.height) / 2; - } else { - offsetY = 0; - } - walk(children[i], (el) => { - el.y += offsetY; - el.contentY += offsetY; - el.x += offsetX; - el.contentX += offsetX; - }); - } - } - - // 获取元素,只会找该元素子级 - getElementBy(key, value) { - let match = []; - walk(this, (element) => { - if (element.options.attrs[key] === value) { - match.push(element); - } - }); - return match - } - - // 添加在最后 - appendChild(element) { - if (!element instanceof Element) throw Error('Unknown Element type') - this.children.push(element); - this._connectChildren(); - this.getLayer().initNode(element); - this.getLayer().flow(); - this.getLayer().repaint(); - return element - } - - // - prependChild(element) { - if (!element instanceof Element) throw Error('Unknown Element type') - this.children.unshift(element); - this._connectChildren(); - this.getLayer().initNode(element); - this.getLayer().flow(); - this.getLayer().repaint(); - return element - } - - removeChild(element) { - if (!element instanceof Element) throw Error('Unknown Element type') - const index = this._getChildren().indexOf(element); - if (index < 0) throw Error('Element must be the child of parent') - const pre = this._getChildren()[index - 1]; - const next = this._getChildren()[index + 1]; - if (pre) { - pre._setSibling(pre.pre, next); - } - if (next) { - next._setSibling(pre, next.next); - } - this.children.splice(index, 1); - element.removeEvent(); - this.getLayer().reflow(); - this.getLayer().repaint(); - } - - append(element) { - if (!element instanceof Element) throw Error('Unknown Element type') - if (!this.parent) throw Error('Can not add element to root level!') - let children = []; - this.parent.children.forEach(child => { - children.push(child); - if (child === this) { - children.push(element); - } - }); - this.parent.children = children; - this.parent._connectChildren(); - this.getLayer().initNode(element); - this.getLayer().flow(); - this.getLayer().repaint(); - } - - prepend(element) { - if (!element instanceof Element) throw Error('Unknown Element type') - if (!this.parent) throw Error('Can not add element to root level!') - let children = []; - for (let i = this.parent.children.length - 1; i >= 0; i--) { - children.unshift(this.parent.children[i]); - if (this.parent.children[i] === this) { - children.unshift(element); - } - } - this.parent.children = children; - this.parent._connectChildren(); - this.getLayer().initNode(element); - this.getLayer().flow(); - this.getLayer().repaint(); - } - - } - - - function getLineHeight(el) { - if (el.renderStyles.display === 'block' || el.renderStyles.display === 'flex') return el.renderStyles.height - // right - let max = 0; - let cur = el; - while (cur && !cur._needNewLine()) { - if (cur.renderStyles.height > max && cur.isInFlow()) { - max = cur.renderStyles.height; - } - cur = cur.next; - } - // left - cur = el; - while (cur && !cur._needNewLine()) { - if (cur.renderStyles.height > max && cur.isInFlow()) { - max = cur.renderStyles.height; - } - cur = cur.pre; - } - - // 最后肯定剩下一个头 - if (cur.renderStyles.height > max) { - max = cur.renderStyles.height; - } - return max - } - - class View extends Element { - _getDefaultStyles() { - return { - ...STYLES.DEFAULT_STYLES, - display: STYLES.DISPLAY.BLOCK - } - } - - _completeStyles() { - super._completeStyles(); - this._completePaddingMargin(); - } - - _completePaddingMargin() { - if (this.styles.padding) { - if (isExact(this.styles.padding)) { - this.styles.paddingLeft = this.styles.padding; - this.styles.paddingBottom = this.styles.padding; - this.styles.paddingRight = this.styles.padding; - this.styles.paddingTop = this.styles.padding; - } else if (Array.isArray(this.styles.padding)) { - // 支持数组[10,20]相当于padding:10px 20px; - if (this.styles.padding.length === 2) { - this.styles.paddingLeft = this.styles.paddingRight = this.styles.padding[1]; - this.styles.paddingBottom = this.styles.paddingTop = this.styles.padding[0]; - } else if (this.styles.padding.length === 4) { - this.styles.paddingLeft = this.styles.padding[3]; - this.styles.paddingBottom = this.styles.padding[2]; - this.styles.paddingRight = this.styles.padding[1]; - this.styles.paddingTop = this.styles.padding[0]; - } - } - } - - if (isExact(this.styles.margin)) { - this.styles.marginLeft = this.styles.margin; - this.styles.marginBottom = this.styles.margin; - this.styles.marginRight = this.styles.margin; - this.styles.marginTop = this.styles.margin; - } else if (Array.isArray(this.styles.margin)) { - // 支持数组[10,20]相当于padding:10px 20px; - if (this.styles.margin.length === 2) { - this.styles.marginLeft = this.styles.marginRight = this.styles.margin[1]; - this.styles.marginBottom = this.styles.marginTop = this.styles.margin[0]; - } else if (this.styles.margin.length === 4) { - this.styles.marginLeft = this.styles.margin[3]; - this.styles.marginBottom = this.styles.margin[2]; - this.styles.marginRight = this.styles.margin[1]; - this.styles.marginTop = this.styles.margin[0]; - } - } - } - - _reflow() { - super._reflow(); - } - - _repaint() { - super._repaint(); - } - - _afterPaint() { - super._afterPaint(); - } - - _drawBackground() { - const { backgroundColor, contentWidth, contentHeight, paddingLeft, paddingRight, paddingTop, paddingBottom, opacity } = this.renderStyles; - const ctx = this.getCtx(); - - if (isExact(opacity)) { - // 绘制透明图 - ctx.globalAlpha = opacity; - } - - this._clip(); - // draw background - if (backgroundColor) { - this.getCtx().fillStyle = backgroundColor; - this.getCtx().fillRect(this.contentX - paddingLeft, this.contentY - paddingTop, contentWidth + paddingLeft + paddingRight, contentHeight + paddingTop + paddingBottom); - } - } - - _drawBox() { - - this._drawRadiusBorder(); - - // for debug - if (this.getLayer().options && this.getLayer().options.debug) { - this.getCtx().strokeStyle = 'green'; - this.getCtx().strokeRect(this.x, this.y, this.renderStyles.width, this.renderStyles.height); - // ctx.strokeStyle = '#fff' - // ctx.strokeText(`${parseInt(this.contentX)} ${parseInt(this.contentY)} ${contentWidth} ${contentHeight}`, this.contentX + 100, this.contentY + 10) - - // - } - - } - - _drawRadiusBorder() { - if (!(this.renderStyles.borderColor || this.renderStyles.shadowBlur)) return - const { contentWidth, contentHeight, paddingLeft, paddingTop, borderStyle, - paddingRight, paddingBottom, shadowBlur, shadowColor, backgroundColor, shadowOffsetX, shadowOffsetY, - borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth } = this.renderStyles; - - const angle = Math.PI / 2; - let borderRadius = this._getBorderRadius(); - - - // 这里是计算画border的位置,起点位置是在线条中间,所以要考虑线条宽度 - let x = this.contentX - this.renderStyles.paddingLeft - borderLeftWidth / 2; - let y = this.contentY - this.renderStyles.paddingTop - borderTopWidth / 2; - let w = contentWidth + paddingLeft + paddingRight + (borderLeftWidth + borderRightWidth) / 2; - let h = contentHeight + paddingTop + paddingBottom + (borderTopWidth + borderBottomWidth) / 2; - - - const topBorder = () => { - // 左上角开始 - this.getCtx().moveTo(x, y + borderRadius); - borderRadius && this.getCtx().arc(x + borderRadius, y + borderRadius, borderRadius, 2 * angle, 3 * angle); - this.getCtx().lineTo(x + w - borderRadius, y); - }; - const rightBorder = () => { - // 右上角 - // this.getCtx().moveTo(x + w - borderRadius, y) - borderRadius && this.getCtx().arc(x + w - borderRadius, y + borderRadius, borderRadius, 3 * angle, 4 * angle); - this.getCtx().lineTo(x + w, y + h - borderRadius); - }; - - const bottomBorder = () => { - // 右下角 - // this.getCtx().moveTo(x + w, y + h - borderRadius) - borderRadius && this.getCtx().arc(x + w - borderRadius, y + h - borderRadius, borderRadius, 0, angle); - this.getCtx().lineTo(x + borderRadius, y + h); - }; - - const leftBorder = () => { - // 左下角 - borderRadius && this.getCtx().arc(x + borderRadius, y + h - borderRadius, borderRadius, angle, angle * 2); - this.getCtx().lineTo(x, y + borderRadius); - }; - - this.getCtx().lineCap = this.renderStyles.lineCap; - this.getCtx().strokeStyle = this.renderStyles.borderColor; - - // 实现虚线 - if (borderStyle && borderStyle !== 'solid') { - if (Array.isArray(borderStyle)) { - this.getCtx().setLineDash(borderStyle); - } else { - this.getCtx().setLineDash([5, 5]); - } - } - - const stroke = (borderWidth) => { - // 有样式则绘制出来 - this.getCtx().lineWidth = borderWidth; - this.getCtx().stroke(); - }; - // 绘制boxshadow - if (shadowColor && shadowBlur) { - this._restore(() => { - this._path(() => { - topBorder(); - rightBorder(); - bottomBorder(); - leftBorder(); - }); - if (isExact(shadowOffsetX)) { - this.getCtx().shadowOffsetX = shadowOffsetX; - } - if (isExact(shadowOffsetY)) { - this.getCtx().shadowOffsetY = shadowOffsetY; - } - this.getCtx().shadowBlur = shadowBlur; - this.getCtx().shadowColor = shadowColor; - this.getCtx().fillStyle = shadowColor; - this.getCtx().fill(); - }); - } - this._restore(() => { - this._path(() => { - - x = this.contentX - this.renderStyles.paddingLeft - borderLeftWidth / 2; - y = this.contentY - this.renderStyles.paddingTop - borderTopWidth / 2; - w = contentWidth + paddingLeft + paddingRight + (borderLeftWidth + borderRightWidth) / 2; - h = contentHeight + paddingTop + paddingBottom + (borderTopWidth + borderBottomWidth) / 2; - if (this.renderStyles.borderTopWidth) { - topBorder(); - stroke(this.renderStyles.borderTopWidth); - } - if (this.renderStyles.borderRightWidth) { - this.getCtx().moveTo(x + w - borderRadius, y); - rightBorder(); - stroke(this.renderStyles.borderRightWidth); - } - if (this.renderStyles.borderBottomWidth) { - this.getCtx().moveTo(x + w, y + h - borderRadius); - bottomBorder(); - stroke(this.renderStyles.borderBottomWidth); - } - if (this.renderStyles.borderLeftWidth) { - this.getCtx().moveTo(x + borderRadius, y + h); - leftBorder(); - stroke(this.renderStyles.borderLeftWidth); - } - }); - }); - - - - - - - } - - _clip() { - if (this.renderStyles.overflow !== 'hidden') return - const { contentWidth, contentHeight, paddingLeft, paddingTop, - paddingRight, paddingBottom, shadowBlur, shadowColor, backgroundColor, - borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidth } = this.renderStyles; - - const angle = Math.PI / 2; - - let borderRadius = this._getBorderRadius(); - - // 为了把border也切进去 - let x = this.contentX - this.renderStyles.paddingLeft - borderLeftWidth; - let y = this.contentY - this.renderStyles.paddingTop - borderTopWidth; - let w = contentWidth + paddingLeft + paddingRight + borderLeftWidth + borderRightWidth; - let h = contentHeight + paddingTop + paddingBottom + borderTopWidth + borderBottomWidth; - - const topBorder = () => { - // 左上角开始 - this.getCtx().moveTo(x, y + borderRadius); - borderRadius && this.getCtx().arc(x + borderRadius, y + borderRadius, borderRadius, 2 * angle, 3 * angle); - this.getCtx().lineTo(x + w - borderRadius, y); - }; - const rightBorder = () => { - // 右上角 - // this.getCtx().moveTo(x + w - borderRadius, y) - borderRadius && this.getCtx().arc(x + w - borderRadius, y + borderRadius, borderRadius, 3 * angle, 4 * angle); - this.getCtx().lineTo(x + w, y + h - borderRadius); - }; - - const bottomBorder = () => { - // 右下角 - // this.getCtx().moveTo(x + w, y + h - borderRadius) - borderRadius && this.getCtx().arc(x + w - borderRadius, y + h - borderRadius, borderRadius, 0, angle); - this.getCtx().lineTo(x + borderRadius, y + h); - }; - - const leftBorder = () => { - // 左下角 - borderRadius && this.getCtx().arc(x + borderRadius, y + h - borderRadius, borderRadius, angle, angle * 2); - this.getCtx().lineTo(x, y + borderRadius); - }; - - this._path(() => { - topBorder(); - rightBorder(); - bottomBorder(); - leftBorder(); - }); - - - this.getCtx().clip(); - - } - - _getBorderRadius() { - const { contentWidth, contentHeight } = this.renderStyles; - let { borderRadius } = this.renderStyles; - if (borderRadius * 2 > contentWidth) { - // 如果大于一半,则角不是90度,统一限制最大为一半 - borderRadius = contentWidth / 2; - } - if (borderRadius * 2 > contentHeight) { - borderRadius = contentHeight / 2; - } - if (borderRadius < 0) borderRadius = 0; - return borderRadius - } - - - - } - - class Text extends Element { - constructor(options, children) { - super(options, children); - this._layout = null; // layout用来保存计算的自身高度 - this._lines = []; - this.children += ''; - } - - _getDefaultStyles() { - return { - ...STYLES.DEFAULT_STYLES, - display: STYLES.DISPLAY.INLINE_BLOCK, - width: STYLES.WIDTH.AUTO, - textAlign: 'left', - } - } - - _completeStyles() { - super._completeStyles(); - } - - _completeWidth() { - super._completeWidth(); - - if (this.styles.textAlign !== 'left' && this.parent && !isAuto(this.parent.styles.width)) { - this.styles.width = '100%'; - } - } - - _initLayout() { - - super._initLayout(); - } - - _measureLayout() { - this._restore(() => { - this.getCtx().font = this._getFont(); - const { width } = this.getCtx().measureText(this.children); - this._layout = { width }; - // 微信 夸克 有兼容性问题 - // this._layout.fontHeight = this._layout.actualBoundingBoxAscent || this.renderStyles.fontSize - this._layout.fontHeight = this.renderStyles.fontSize; - this._layout.height = this.renderStyles.lineHeight; - this._calcLine(); - }); - return this._layout - } - - _drawContent() { - const { color, contentWidth, lineHeight, textAlign, textIndent } = this.renderStyles; - let x = this.contentX; - this.getCtx().fillStyle = color; - this.getCtx().textAlign = textAlign; - this.getCtx().font = this._getFont(); - if (textAlign === STYLES.TEXT_ALIGN.RIGHT) { - x = this.contentX + contentWidth; - } else if (textAlign === STYLES.TEXT_ALIGN.CENTER) { - x = this.contentX + (contentWidth / 2); - } - let _x = x; - this._lines.forEach((line, index) => { - if (index === 0 && textIndent) { - // 第一行实现textIndent - _x = x + textIndent; - } else { - _x = x; - } - this.getCtx().fillText(line, _x, (this.contentY + this._layout.fontHeight + ((lineHeight - this._layout.fontHeight) / 2) + lineHeight * index) - 1); - }); - } - - _getFont() { - const { fontSize, fontWeight, fontFamily } = this.renderStyles; - return `${fontWeight} ${fontSize}px ${fontFamily}` - } - - _calcLine() { - if (!this.parent || !this.children) return - const { width: textWidth, height: textHeight } = this._layout; - const { contentWidth: parentContentWidth } = this.parent.renderStyles; - const { width: parentWidth } = this.parent.styles; - // 如果一行宽度够,或者父级宽度是auto - if ((isExact(parentContentWidth) && parentContentWidth >= textWidth) || parentWidth === STYLES.WIDTH.AUTO) { - this._lines = [this.children]; - } else { - this._lines = []; - let lineIndex = 1; - let lineText = ''; - let _layout = null; - for (let i = 0; i < this.children.length; i++) { - _layout = this.getCtx().measureText(lineText + this.children[i]); - if (_layout.width > parentContentWidth) { - if (lineIndex >= this.renderStyles.maxLine) { - // 最大行数限制 以及maxline省略号实现 - lineText = lineText.substring(0, lineText.length - 2) + '...'; - break - } - // 超出了 - this._lines.push(lineText); - lineText = ''; - lineIndex += 1; - - } - - lineText += this.children[i]; - } - this._lines.push(lineText); - this._layout.width = parentContentWidth; - // 根据lineheihgt更新height - this._layout.height = this._lines.length * this.renderStyles.lineHeight; - } - } - } - - class $Image extends View { - - init() { - super.init(); - this._imageInfo = { - width: 0, - height: 0, - sx:0, - sy:0, - swidth:0, - sheight:0, - dx:0, - dy:0, - dwidth:0, - dheight:0 - }; - this._image = null; - this._loadImage(); - } - - _loadImage() { - return new Promise((resolve, reject) => { - loadImage(this.options.attrs.src, this.getLayer().getCanvas()) - .then(({ info, image }) => { - this._imageInfo = info; - this._image = image; - resolve(); - - this._layoutImage(); - - // // 重新布局绘制 - // this.getLayer().reflow() - // this.getLayer().repaint() - - // call load callback - if(this.options.on && this.options.on.load){ - this.options.on.load(this); - } - }); - }) - } - - _drawContent() { - if (!this._image) return - const { contentWidth, contentHeight } = this.renderStyles; - const {mode} = this.options.attrs; - const {sx,sy,swidth,sheight,dx,dy,dwidth,dheight,width:imageW,height:imageH} = this._imageInfo; - if(mode === 'aspectFill'){ - this.getCtx().drawImage(this._image,sx,sy,swidth,sheight, this.contentX, this.contentY, contentWidth, contentHeight); - }else if(mode === 'aspectFit'){ - this.getCtx().drawImage(this._image,0,0,imageW,imageH,dx,dy,dwidth,dheight); - }else { - this.getCtx().drawImage(this._image, this.contentX, this.contentY, contentWidth, contentHeight); - } - } - - // 计算图片布局 - _layoutImage() { - const { contentWidth, contentHeight } = this.renderStyles; - const {mode} = this.options.attrs; - const { width, height } = this.styles; - const {width:imageW,height:imageH} = this._imageInfo; - // 根据用户设置判断图片宽高,目前支持widthfix、heightfix、平铺 - let w = contentWidth; - let h = contentHeight; - if (!isAuto(width) && isAuto(height)) { - // width fix - w = contentWidth; - h = getHeightByWidth(w, imageW, imageH); - } else if (!isAuto(height) && isAuto(width)) { - // height fix - h = contentHeight; - w = getWidthByHeight(h, imageW, imageH); - } else if (isAuto(width) && isAuto(height)) { - // auto - w = imageW; - h = imageH; - }else if(mode === 'aspectFill'){ - // 填充 - if((w/h) > (imageW/imageH)){ - this._imageInfo.swidth = imageW; - this._imageInfo.sheight = getHeightByWidth(imageW,w,h); - this._imageInfo.sx = 0; - this._imageInfo.sy = (imageH - this._imageInfo.sheight)/2; - }else { - this._imageInfo.sheight = imageH; - this._imageInfo.swidth = getWidthByHeight(imageH,contentWidth,contentHeight); - this._imageInfo.sy = 0; - this._imageInfo.sx = (imageW - this._imageInfo.swidth)/2; - } - }else if(mode === 'aspectFit'){ - if((w/h) > (imageW/imageH)){ - this._imageInfo.dwidth = getWidthByHeight(contentHeight,imageW,imageH); - this._imageInfo.dheight = contentHeight; - this._imageInfo.dy = this.contentY; - this._imageInfo.dx = (contentWidth - this._imageInfo.dwidth)/2 + this.contentX; - }else { - this._imageInfo.dheight = getHeightByWidth(contentWidth,imageW,imageH); - this._imageInfo.dwidth = contentWidth; - this._imageInfo.dx = this.contentX; - this._imageInfo.dy = (contentHeight - this._imageInfo.dheight)/2 + this.contentY; - } - }else { - w = contentWidth; - h = contentHeight; - } - this._layout = {width:w,h:height}; - } - - _measureLayout(){ - if(this._layout){ - return this._layout - }else { - return { - width:this.renderStyles.width, - height:this.renderStyles.height - } - } - } - - } - - // canvas可能为空,小程序下必传 - function loadImage(src, canvas) { - - return new Promise((resolve, reject) => { - let image = null; - - if (isWX()) { - image = canvas.createImage(); - } else { - image = new Image(); - } - - image.src = src; - image.onload = function (e) { - resolve({ - image, - info: { - width: e.target.width, - height: e.target.height - } - }); - }; - - }) - } - - function getWidthByHeight(height, originWidth, originHeight) { - return height / originHeight * originWidth - } - - function getHeightByWidth(width, originWidth, originHeight) { - return width / originWidth * originHeight - } - - class EventManager { - - constructor({ simulateClick = true }) { - this.clickList = []; - this.touchstartList = []; - this.touchmoveList = []; - this.touchendList = []; - this.touchStartEvent = null; - this.simulateClick = simulateClick; // 是否模拟移动端点击事件 - } - - clear() { - this.clickList = []; - this.touchstartList = []; - this.touchmoveList = []; - this.touchendList = []; - } - - click(x, y) { - let event = new Event({ x, y, type: 'click' }); - this._emit(event); - } - - touchstart(x, y) { - let event = new Event({ x, y, type: 'touchstart' }); - this.touchStartEvent = event; - this._emit(event); - } - - touchmove(x, y) { - let event = new Event({ x, y, type: 'touchmove' }); - this._emit(event); - } - - touchend(x, y) { - let event = new Event({ x, y, type: 'touchend' }); - this._emit(event); - this.checkClick(event); - } - - _emit(e) { - let callbackList = []; - switch (e.type) { - case 'click': callbackList = this.clickList; break - case 'touchstart': callbackList = this.touchstartList; break - case 'touchmove': callbackList = this.touchmoveList; break - case 'touchend': callbackList = this.touchendList; break - } - - // let emitList = [] - - // for(let i = callbackList.length-1; i>=0; i--){ - // if(this.isPointInElement()) - // } - - for (let i = 0; i < callbackList.length; i++) { - if (this.isPointInElement(e.x, e.y, callbackList[i].element)) { - if (!e.currentTarget) e.currentTarget = callbackList[i].element; - callbackList[i].callback(e); - if (e.cancelBubble) break - } - } - } - - // 待优化 - isPointInElement(x, y, element) { - let { width, height } = element.renderStyles; - let _x = x; // 根据scroll-view做转换 - let _y = y; - let cur = element.parent; - // 这里实现有点拉跨,每次都会向上遍历看有没有scroll-view - while (cur) { - if (cur.type === 'scroll-view' && this._isPointTouchElement(_x,_y,cur)) { - if (cur.styles.direction === 'x') { - _x -= cur.currentScroll; - } else { - _y -= cur.currentScroll; - } - } - cur = cur.parent; - } - - return this._isPointTouchElement(_x,_y,element) - } - - _isPointTouchElement(x,y,element){ - if (x >= element.x && y >= element.y && (x <= element.x + element.renderStyles.width) && (y <= element.y + element.renderStyles.height)) { - return true - } - return false - } - - createEvent(x, y, type) { - - } - - removeElement(element) { - this.clickList = this.clickList.filter(item => item.element !== element); - this.touchstartList = this.touchstartList.filter(item => item.element !== element); - this.touchmoveList = this.touchmoveList.filter(item => item.element !== element); - this.touchendList = this.touchendList.filter(item => item.element !== element); - } - - onClick(callback, element) { - // 为啥要unshift呢,因为元素是从父级,往子集初始化的 - this.clickList.unshift({ callback, element }); - } - - onTouchStart(callback, element) { - this.touchstartList.unshift({ callback, element }); - } - - onTouchMove(callback, element) { - this.touchmoveList.unshift({ callback, element }); - } - - onTouchEnd(callback, element) { - this.touchendList.unshift({ callback, element }); - } - - // 这里利用touchstart和touchend实现了移动端click事件 - checkClick(event) { - if (this.touchStartEvent && this.simulateClick) { - // 判断两点距离 - let { x: startx, y: starty } = this.touchStartEvent; - let { x: endx, y: endy } = event; - let distance = ((endy * endy + endx * endx) - (starty * starty + startx * startx)); - if (distance < 10 && distance > -10) { - this.click(endx, endy); - } - } - } - } - - class Event { - constructor({ x, y, type }) { - this.x = x; - this.y = y; - this.scrollX = x; // scroll到每一层scrollview会不断变化 - this.scrollY = y; - this.type = type; - this.cancelBubble = false; - this.currentTarget = null; // 第一个element - } - - // 阻止冒泡 - stopPropagation() { - this.cancelBubble = true; - } - } - - class Layer { - constructor(ctx, options) { - this.ctx = ctx; - this.node = null; - this.nodeList = []; - this.p2cList = []; - this.c2pList = []; - this.renderList = []; - this.options = options; - this.eventManager = new EventManager(options); - } - - update(ctx, options) { - this.ctx = ctx; - this.options = options; - this.node.container = this.options; - } - - mountNode(node) { - this.node = node; - this.node.root = this.node; - this.node.layer = this; - this.node.container = this.options; - // 事件也清空一下,重新挂载 - this.eventManager.clear(); - this.initRender(); - } - - initRender() { - this.connectChildren(this.node); - this.p2cList = this.getP2CList(this.node); - this.c2pList = breadthFirstSearchRight(this.node).reverse(); - // this.initPaintList() - this.initNode(); - console.log(this.p2cList); - console.log(this.c2pList); - - - this.flow(); - - // inline-block等还需要再重新排一次,待优化 - // this.reflow() - - this.repaint(); - - } - - getP2CList(el) { - // 广度优先 - return breadthFirstSearch(el) - - } - - connectChildren(el) { - if (el.hasChildren()) { - let childrenRender = el._getChildren().map((child, index) => { - // 设置parent - child._setParent(el); - // 设置了上一个兄弟节点 - child._setSibling(el._getChildren()[index - 1], el._getChildren()[index + 1]); - return this.connectChildren(child) - }).reduce((sum, val) => [...sum, ...val]); - // childrenRender.reverse() - return [el._generateRender(), ...childrenRender] - } else { - return [el._generateRender()] - } - } - - - initNode(node = this.node) { - walk(node, item => { - item.init(); - }); - } - - flow(node = this.node) { - this.reflow(); - } - - initPaintList() { - // 这里实现index - this.renderList = this.nodeList; - } - - reflow(node = this.node) { - for (let i = 0; i < this.c2pList.length; i++) { - this.c2pList[i]._initWidthHeight(); - } - - for (let i = 0; i < this.p2cList.length; i++) { - this.p2cList[i]._initPosition(); - } - } - - /** - * 可以给定element,则只会重绘element所在的区域 - * @param {Element} element - */ - repaint(element = this.node) { - // let width = element.renderStyles.width - // let height = element.renderStyles.height - // let x = element.x - // let y = element.y - // this.ctx.clearRect(x, y, width, height) - // walk(element, (item) => { - // item._repaint() - // item._afterPaint() - // }) - - // this.ctx.clearRect(this.container.x, this.node.y, this.node.renderStyles.width, this.node.renderStyles.height) - this.ctx.clearRect(0, 0, this.options.width, this.options.height); - walk(this.node, element => { - element._repaint(); - element._afterPaint(); - }); - // 兼容小程序 - this.ctx.draw && this.ctx.draw(); - } - - // 小程序需要 - getCanvas() { - return this.options && this.options.canvas - } - - } - - - function breadthFirstSearch(node, reverse = false) { - - var nodes = []; - - if (node != null) { - - var queue = []; - - queue.unshift(node); - - while (queue.length != 0) { - - var item = queue.shift(); - - nodes.push(item._generateRender()); - - var children = item._getChildren(); - - for (var i = 0; i < children.length; i++) - queue.push(children[i]._generateRender()); - - } - - } - - return nodes; - - } - - function breadthFirstSearchRight(node) { - - var nodes = []; - - if (node != null) { - - var queue = []; - - queue.unshift(node); - - while (queue.length != 0) { - - var item = queue.shift(); - - nodes.push(item._generateRender()); - - var children = item._getChildren(); - - for (var i = children.length - 1; i >= 0; i--) - queue.push(children[i]._generateRender()); - - } - - } - - return nodes; - - } - - class ScrollView extends View { - - constructor(options, children) { - super(options, children); - // 外面包裹一层容器,内层的滚动 - options.styles.overflow = 'hidden'; - this.type = 'scroll-view'; - this._scrollView = new View(options, [this]); - this._scrollView.type = 'scroll-view-container'; - return this._scrollView - } - - _getDefaultStyles() { - return { - ...STYLES.DEFAULT_STYLES, - direction: 'y', - } - } - - init() { - super.init(); - this.addEventListener(); - - const { height, width, direction } = this.styles; - if (direction === 'y') { - if (!isAuto(height)) { - this.styles.height = 'auto'; - this.renderStyles.height = 'auto'; - } else { - // 必须设置 - console.error('scroll-view 必须设置明确的高度'); - } - } else if (direction === 'x') { - if (!isAuto(width)) { - this.styles.width = 'auto'; - this.renderStyles.width = 'auto'; - } else { - // 必须设置 - console.error('scroll-view 必须设置明确的宽度'); - } - } - } - - addEventListener() { - // 监听滚动 - this.currentScroll = 0; - let direction = this.styles.direction; - let start = 0; - let lastStart = 0; - let startMove = false; - let offset = 0; - let speed = 0; - let glideInterval = null; - let resistance = 1; - - // this.getLayer().eventManager.captureTouch(e => { - // if(direction === 'y'){ - // e.scrollY -= this.currentScroll - // }else{ - // e.scrollX -= this.currentScroll - // } - // },this._scrollView) - - this.getLayer().eventManager.onTouchStart((e) => { - e.stopPropagation(); - start = e[direction]; - lastStart = start; - startMove = true; - clearInterval(glideInterval); - }, this._scrollView); - this.getLayer().eventManager.onTouchMove((e) => { - if (startMove) { - e.stopPropagation(); - offset = (e[direction] - start); - if (this.scrollBy(offset)) { - lastStart = start; - start = e[direction]; - } - } - }, this._scrollView); - this.getLayer().eventManager.onTouchEnd((e) => { - if (startMove) { - startMove = false; - speed = (e[direction] - lastStart) * 2; - resistance = -speed * 0.02; - clearInterval(glideInterval); - glideInterval = setInterval(() => { - if (!this.scrollBy(speed)) { - clearInterval(glideInterval); - } - speed += resistance; - if (speed * speed <= 0.05) { - speed = 0; - clearInterval(glideInterval); - } - }, 18); - } - }, this._scrollView); - } - - _repaint() { - // 滚动实现 目前是计算一次重新绘制一次,有需要再优化 - const { direction } = this.renderStyles; - if (direction === 'y') { - this.getCtx().translate(0, this.currentScroll); - } else { - this.getCtx().translate(this.currentScroll, 0); - } - super._repaint(); - } - - calcScrollBound(offset) { - const { contentWidth: offsetWidth, contentHeight: offsetHeight } = this._scrollView.renderStyles; - const { width: scrollWidth, height: scrollHeight, direction } = this.renderStyles; - if (direction === 'y') { - if ((offsetHeight - this.currentScroll - offset) > scrollHeight) { - return false - } else if (this.currentScroll + offset > 0) { - return false - } - } else { - if ((offsetWidth - this.currentScroll - offset) > scrollWidth) { - return false - } else if (this.currentScroll + offset > 0) { - return false - } - } - - return true - } - - scrollBy(offset) { - if (this.calcScrollBound(offset)) { - this.currentScroll += offset; - this.getLayer().repaint(); - // this.getLayer().repaint(this._scrollView) - return true - } else { - return false - } - } - - scrollTo(pos) { - - } - - } - - /** - * 生成一个element tree - * @param {String} name - * @param {Function} options - */ - - const elementFactory = {}; - // - registerComponent('view', (options, children) => new View(options, children)); - registerComponent('text', (options, children) => new Text(options, children)); - registerComponent('image', (options, children) => new $Image(options, children)); - registerComponent('scroll-view', (options, children) => new ScrollView(options, children)); - registerComponent('scrollview', (options, children) => new ScrollView(options, children)); - - function createElement(model) { - // 生成树 - function c(name, options = {}, children = []) { - // if (arguments.length < 3) { - // throw Error(`Element [${name}]: need 3 argument but get 2`) - // } - let _element = null; - let _children = children; - if (elementFactory[name]) { - // if (typeof children === 'string' && name !== 'text') { - // // 支持text简写 - // _children = new Text({}, children) - // } else if (!Array.isArray(children)) { - // throw Error(`Element [${name}]:Children must be type of Array!`) - // } - _element = elementFactory[name](options, _children, c); - } else { - throw Error(`Unknown tag name [${name}] !`) - } - return _element - } - const _model = model(c); - // 挂载children - return _model - } - - function createLayer(ctx, options) { - return new Layer(ctx, options) - } - - // 注册全局组件 - function registerComponent(name, factory) { - if (elementFactory[name]) { - throw Error(`Already exist tag name [${name}] !`) - } - elementFactory[name] = factory; - } - - const ef = { - createLayer, - createElement, - component: registerComponent, - View, - Text, - Image: $Image, - Layer, - ScrollView - }; - - return ef; - -}))); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).easyCanvas=e()}(this,(function(){"use strict";const t={BLOCK:"block",INLINE_BLOCK:"inline-block",INLINE:"inline",FLEX:"flex",NONE:"none"},e={AUTO:"auto",OUTER:"100%"},i={ROW:"row",COLUMN:"column"};var s={DISPLAY:t,WIDTH:e,POSITION:{ABSOLUTE:"absolute",FIXED:"fixed",RELATIVE:"relative",STATIC:"static"},DEFAULT_STYLES:{display:t.BLOCK,fontSize:14,fontWeight:400,fontFamily:"Microsoft Yahei",color:"#000",paddingTop:0,paddingBottom:0,paddingLeft:0,paddingRight:0,marginTop:0,marginBottom:0,marginLeft:0,marginRight:0,height:e.AUTO,borderRadius:0,lineCap:"square",flexDirection:i.ROW,verticalAlign:"top",textAlign:"left",justifyContent:"flex-start",alignItems:"flex-start",whiteSpace:"normal",zIndex:1},TEXT_ALIGN:{LEFT:"left",RIGHT:"right",CENTER:"center"},FLEX_DIRECTION:i};function n(t){return"number"==typeof t}function h(t){return"auto"===t}function r(t){if("string"==typeof t)return t.match("%")}function o(t){let e=parseInt(t.replace("%",""));return isNaN(e)||e<0?0:e/100}function l(t,e){e(t),t.hasChildren()&&t._getChildren().forEach(t=>{l(t,e)})}class d{constructor(){this.width=0,this.height=0,this.contentWidth=0,this.y=0,this.doorClosed=!1,this.outerWidth=0,this.container=null,this.elements=[],this.start=null,this.end=null,this.offsetX=0,this.id=Math.random()}bind(t){this.container=t.parent,this.initHeight(t),this.outerWidth=t.parent&&h(t.parent.styles.width)?1/0:t.parent.renderStyles.contentWidth,this.start=t,this.add(t)}initHeight(t){this.height=t.parent&&t.parent.renderStyles.lineHeight||0}initLayout(t){this.right=t._getContainerLayout().contentX,this.y=this.getPreLineBottom(t)}refreshElementPosition(t){this.start===t&&this.initLayout(t),t.x=this.right+this.offsetX,t.y=this.y+this.getOffsetY(t),this.right+=t.renderStyles.width}getOffsetY(t){return"bottom"===t.renderStyles.verticalAlign?this.height-t.renderStyles.height:"middle"===t.renderStyles.verticalAlign?(this.height-t.renderStyles.height)/2:0}add(t){this.elements.push(t),t.line=this,this.refreshWidthHeight(t),t.next||this.closeLine()}refreshWidthHeight(t){t.renderStyles.height>this.height&&(this.height=t.renderStyles.height),this.width+=t.renderStyles.width}canIEnter(t){return!(t.renderStyles.width+this.width>this.outerWidth)||(this.closeLine(),!1)}closeLine(){this.end=this.elements[this.elements.length-1],this.refreshXAlign()}getPreLineBottom(t){return t.pre?t.pre.line?t.pre.line.height+t.pre.line.y:t._getPreLayout().y+t._getPreLayout().height:t._getContainerLayout().contentY}refreshXAlign(){if(this.outerWidth>5e3)return;if(!this.end.parent)return;let t=this.outerWidth-this.width;"center"===this.end.parent.renderStyles.textAlign?t/=2:"left"===this.end.parent.renderStyles.textAlign&&(t=0),this.offsetX=t}}class a extends d{constructor(){super(),this.exactValue=0,this.flexTotal=0}closeLine(){super.closeLine(),this.calcFlex()}add(t){n(t.styles.flex)?this.flexTotal+=t.styles.flex:n(t.styles.width)&&(this.exactValue+=t.styles.width),super.add(t)}initHeight(){this.height=0}calcFlex(){const{contentWidth:t}=this.container.renderStyles;this.elements.forEach(e=>{n(e.styles.flex)&&(e.renderStyles.width=e.styles.flex/this.flexTotal*(t-this.exactValue),e._refreshContentWithLayout())})}refreshXAlign(){if(!this.end.parent)return;let t=this.outerWidth-this.width;"center"===this.end.parent.renderStyles.justifyContent?t/=2:"flex-start"===this.end.parent.renderStyles.justifyContent&&(t=0),this.offsetX=t}getOffsetY(t){return"flex-end"===t.renderStyles.alignSelf?this.container.renderStyles.contentHeight-t.renderStyles.height:"center"===t.renderStyles.alignSelf?(this.container.renderStyles.contentHeight-t.renderStyles.height)/2:0}}class g{constructor(t,e){this.options=Object.assign({attrs:{},styles:{},on:{}},t),this.children=e,this.styles=null,this.parent=null,this.renderStyles=null,this.x=0,this.y=0,this.pre=null,this.next=null,this.render=null,this.root=null,this.container=null}init(){this._initStyles(),this.initEvent()}initEvent(){const{click:t}=this.options.on;if(t){const{click:t}=this.options.on;this.getLayer().eventManager.onClick(t,this)}}removeEvent(){this.getLayer().eventManager.removeElement(this)}getLayer(){return this.root.layer}mount(t){t.mountNode(this)}_restore(t){this.getCtx().save(),t(),this.getCtx().restore()}_path(t){this.getCtx().beginPath(),t(),this.getCtx().closePath()}_initStyles(){this.styles=Object.assign({},this._getDefaultStyles(),this._getParentStyles(this.options.styles),this.options.styles||{}),this._completeStyles(),this._initRenderStyles()}_initRenderStyles(){const t={...this.styles},e=this._getContainerLayout().contentWidth,i=this._getContainerLayout().contentHeight;h(t.width)?t.width=0:r(t.width)&&(t.width=o(t.width)*e),h(t.height)?t.height=0:r(t.height)&&(t.height=o(t.height)*i),t.width||(t.width=0),t.height||(t.height=0),t.contentWidth=t.width-t.paddingLeft-t.paddingRight-t.marginLeft-t.marginRight-this._getTotalBorderWidth(t),t.contentHeight=t.height-t.paddingTop-t.paddingBottom-t.marginTop-t.marginBottom-this._getTotalBorderWidth(t),this.renderStyles=t,this._InFlexBox()&&this._bindFlexBox()}_getParentStyles(t){let{textAlign:e,lineHeight:i,fontSize:s,color:n,fontFamily:h,alignItems:r}=this.parent&&this.parent.styles||{},o={};return i&&(o.lineHeight=i),s&&(o.fontSize=s),n&&(o.color=n),h&&(o.fontFamily=h),r&&!t.alignSelf&&(o.alignSelf=r),o}_completeStyles(){this._completeFlex(),this._completeWidth(),this._completeBorder(),this._completeFont()}_completeBorder(){let{borderWidth:t,borderLeftWidth:e,borderRightWidth:i,borderBottomWidth:s,borderTopWidth:n,borderRadius:h}=this.styles;t||(this.styles.borderWidth=0,t=0),Array.isArray(t)?(this.styles.borderTopWidth=t[0],this.styles.borderRightWidth=t[1],this.styles.borderBottomWidth=t[2],this.styles.borderLeftWidth=t[3]):(e||(this.styles.borderLeftWidth=t),i||(this.styles.borderRightWidth=t),s||(this.styles.borderBottomWidth=t),n||(this.styles.borderTopWidth=t)),h&&(this.styles.overflow="hidden")}_completeWidth(){this.styles.width||(this.styles.display!==s.DISPLAY.INLINE_BLOCK&&this.styles.display!==s.DISPLAY.INLINE&&this.isInFlow()?this.styles.display===s.DISPLAY.BLOCK||this.styles.display===s.DISPLAY.FLEX?this.styles.width=s.WIDTH.OUTER:this.styles.width=0:this.styles.width=s.WIDTH.AUTO)}_completeFont(){this.styles.fontSize&&!this.styles.lineHeight?this.styles.lineHeight=1.4*this.styles.fontSize:this.styles.lineHeight||(this.styles.lineHeight=14)}_completeFlex(){this.parent&&this.parent.styles.display===s.DISPLAY.FLEX&&(this.styles.width||this.styles.flex||(this.styles.flex=1))}_getDefaultStyles(){return s.DEFAULT_STYLES}hasChildren(){return!(!Array.isArray(this.children)||!this.children.length)}_getChildren(){return this.hasChildren()?this.children:[]}_getChildrenInFlow(){return this._getChildren().filter(t=>t.isInFlow())}isInFlow(){const{position:t,display:e}=this.styles;return t!==s.POSITION.ABSOLUTE&&t!==s.POSITION.FIXED}_setParent(t){this.parent=t,this.root=t.root}_setSibling(t,e){this.pre=t,this.next=e}_generateRender(){return this}getCtx(){return this.root.layer.ctx}_reflow(){}_repaint(){this.getCtx().save(),this._drawBox(),this._drawBackground(),this._drawContent()}_afterPaint(){if(this.hasChildren()||this.getCtx().restore(),(t=this).parent&&!t.next&&!t.hasChildren()){this.getCtx().restore();let t=this.parent;for(;t&&!t.next;)this.getCtx().restore(),t=t.parent}var t}_drawBox(){}_drawContent(){}_drawBackground(){}_initWidthHeight(){const{width:t,height:e,display:i,flex:n,marginLeft:r,marginRight:o,marginTop:l,marginBottom:d}=this.styles,a=this._measureLayout();h(t)&&(this.renderStyles.contentWidth=a.width),h(e)&&(this.renderStyles.contentHeight=a.height),this._refreshLayoutWithContent(),i===s.DISPLAY.INLINE_BLOCK?this._bindLine():this._InFlexBox()&&this.line.refreshWidthHeight(this)}_initPosition(){const{contentX:t,contentY:e,contentWidth:i,contentHeight:h}=this._getContainerLayout(),{paddingLeft:r,paddingTop:o,borderLeftWidth:l,borderTopWidth:d,marginLeft:a,marginTop:g}=this.renderStyles;if(this.isInFlow())this._InFlexBox()||this.renderStyles.display===s.DISPLAY.INLINE_BLOCK?this.line.refreshElementPosition(this):(this.x=t,this.y=this._getPreLayout().y+this._getPreLayout().height);else{const{top:s,bottom:r,right:o,left:l,width:d,height:a}=this.renderStyles;n(s)?this.y=e+s:n(r)&&(this.y=e+h-r-a),n(l)?this.x=t+l:n(o)&&(this.x=t+i-o-d)}this.contentX=this.x+r+l+a,this.contentY=this.y+o+d+g}_InFlexBox(){return!!this.parent&&(!(!this.parent||this.parent.renderStyles.display!==s.DISPLAY.FLEX)||void 0)}_refreshLayoutWithContent(){this.renderStyles.height=this.renderStyles.contentHeight+this.renderStyles.paddingTop+this.renderStyles.paddingBottom+this.renderStyles.marginTop+this.renderStyles.marginBottom+this._getTotalBorderWidth(),this.renderStyles.width=this.renderStyles.contentWidth+this.renderStyles.paddingLeft+this.renderStyles.paddingRight+this.renderStyles.marginLeft+this.renderStyles.marginRight+this._getTotalBorderWidth()}_refreshContentWithLayout(){this.renderStyles.contentHeight=this.renderStyles.height-this.renderStyles.paddingTop-this.renderStyles.paddingBottom-this.renderStyles.marginTop-this.renderStyles.marginBottom-this._getTotalBorderWidth(),this.renderStyles.contentWidth=this.renderStyles.width-this.renderStyles.paddingLeft-this.renderStyles.paddingRight-this.renderStyles.marginLeft-this.renderStyles.marginRight-this._getTotalBorderWidth()}_getTotalBorderWidth(t=this.renderStyles){return t.borderLeftWidth+t.borderRightWidth+t.borderTopWidth+t.borderBottomWidth}_bindLine(){this.pre&&this.pre.line&&this.pre.line.canIEnter(this)?this.pre.line.add(this):(new d).bind(this)}_bindFlexBox(){this.pre&&this.pre.line?this.pre.line.add(this):(new a).bind(this)}_getContainerLayout(){let t=this.parent;return this.styles.position,s.POSITION.STATIC,t||(t={renderStyles:{width:this.container.width,height:this.container.height,paddingTop:0,paddingBottom:0,paddingLeft:0,paddingRight:0,marginLeft:0,marginRight:0,marginTop:0,marginBottom:0,contentWidth:this.container.width,contentHeight:this.container.height},x:0,y:0,contentX:0,contentY:0}),{width:t.renderStyles.width,height:t.renderStyles.height,x:t.x,y:t.y,paddingTop:t.renderStyles.paddingTop,paddingBottom:t.renderStyles.paddingBottom,paddingLeft:t.renderStyles.paddingLeft,paddingRight:t.renderStyles.paddingRight,marginLeft:t.renderStyles.marginLeft,marginRight:t.renderStyles.marginRight,marginTop:t.renderStyles.marginTop,marginBottom:t.renderStyles.marginBottom,contentX:t.contentX,contentY:t.contentY,contentWidth:t.renderStyles.contentWidth,contentHeight:t.renderStyles.contentHeight}}_getPreLayout(){let t=this.pre;for(;t&&!t.isInFlow();)t=t.pre;return t?{width:t.renderStyles.width,height:t.renderStyles.height,x:t.x,y:t.y}:{width:0,height:0,x:this._getContainerLayout().contentX,y:this._getContainerLayout().contentY}}_measureLayout(){let t=0,e=0;return this._getChildrenInFlow().forEach(i=>{i.line?i.line.start===i&&(i.line.width>t&&(t=i.line.width),e+=i.line.height):i.renderStyles.width>t?(t=i.renderStyles.width,e+=i.renderStyles.height):e+=i.renderStyles.height}),{width:t,height:e}}getElementBy(t,e){let i=[];return l(this,s=>{s.options.attrs[t]===e&&i.push(s)}),i}appendChild(t){if(!t instanceof g)throw Error("Unknown Element type");return this.children.push(t),this._connectChildren(),this.getLayer().initNode(t),this.getLayer().flow(),this.getLayer().repaint(),t}prependChild(t){if(!t instanceof g)throw Error("Unknown Element type");return this.children.unshift(t),this._connectChildren(),this.getLayer().initNode(t),this.getLayer().flow(),this.getLayer().repaint(),t}removeChild(t){if(!t instanceof g)throw Error("Unknown Element type");const e=this._getChildren().indexOf(t);if(e<0)throw Error("Element must be the child of parent");const i=this._getChildren()[e-1],s=this._getChildren()[e+1];i&&i._setSibling(i.pre,s),s&&s._setSibling(i,s.next),this.children.splice(e,1),t.removeEvent(),this.getLayer().reflow(),this.getLayer().repaint()}append(t){if(!t instanceof g)throw Error("Unknown Element type");if(!this.parent)throw Error("Can not add element to root level!");let e=[];this.parent.children.forEach(i=>{e.push(i),i===this&&e.push(t)}),this.parent.children=e,this.parent._connectChildren(),this.getLayer().initNode(t),this.getLayer().flow(),this.getLayer().repaint()}prepend(t){if(!t instanceof g)throw Error("Unknown Element type");if(!this.parent)throw Error("Can not add element to root level!");let e=[];for(let i=this.parent.children.length-1;i>=0;i--)e.unshift(this.parent.children[i]),this.parent.children[i]===this&&e.unshift(t);this.parent.children=e,this.parent._connectChildren(),this.getLayer().initNode(t),this.getLayer().flow(),this.getLayer().repaint()}}class c extends g{_getDefaultStyles(){return{...s.DEFAULT_STYLES,display:s.DISPLAY.BLOCK}}_completeStyles(){super._completeStyles(),this._completePaddingMargin()}_completePaddingMargin(){this.styles.padding&&(n(this.styles.padding)?(this.styles.paddingLeft=this.styles.padding,this.styles.paddingBottom=this.styles.padding,this.styles.paddingRight=this.styles.padding,this.styles.paddingTop=this.styles.padding):Array.isArray(this.styles.padding)&&(2===this.styles.padding.length?(this.styles.paddingLeft=this.styles.paddingRight=this.styles.padding[1],this.styles.paddingBottom=this.styles.paddingTop=this.styles.padding[0]):4===this.styles.padding.length&&(this.styles.paddingLeft=this.styles.padding[3],this.styles.paddingBottom=this.styles.padding[2],this.styles.paddingRight=this.styles.padding[1],this.styles.paddingTop=this.styles.padding[0]))),n(this.styles.margin)?(this.styles.marginLeft=this.styles.margin,this.styles.marginBottom=this.styles.margin,this.styles.marginRight=this.styles.margin,this.styles.marginTop=this.styles.margin):Array.isArray(this.styles.margin)&&(2===this.styles.margin.length?(this.styles.marginLeft=this.styles.marginRight=this.styles.margin[1],this.styles.marginBottom=this.styles.marginTop=this.styles.margin[0]):4===this.styles.margin.length&&(this.styles.marginLeft=this.styles.margin[3],this.styles.marginBottom=this.styles.margin[2],this.styles.marginRight=this.styles.margin[1],this.styles.marginTop=this.styles.margin[0]))}_reflow(){super._reflow()}_repaint(){super._repaint()}_afterPaint(){super._afterPaint()}_drawBackground(){const{backgroundColor:t,contentWidth:e,contentHeight:i,paddingLeft:s,paddingRight:h,paddingTop:r,paddingBottom:o,opacity:l}=this.renderStyles,d=this.getCtx();n(l)&&(d.globalAlpha=l),this._clip(),t&&(this.getCtx().fillStyle=t,this.getCtx().fillRect(this.contentX-s,this.contentY-r,e+s+h,i+r+o)),this.getLayer().options&&this.getLayer().options.debug&&(this.getCtx().strokeStyle="green",this.getCtx().strokeRect(this.contentX,this.contentY,this.renderStyles.contentWidth,this.renderStyles.contentHeight))}_drawBox(){this._drawRadiusBorder()}_drawRadiusBorder(){if(!this.renderStyles.borderColor&&!this.renderStyles.shadowBlur)return;const{contentWidth:t,contentHeight:e,paddingLeft:i,paddingTop:s,borderStyle:h,paddingRight:r,paddingBottom:o,shadowBlur:l,shadowColor:d,backgroundColor:a,shadowOffsetX:g,shadowOffsetY:c,borderLeftWidth:y,borderRightWidth:p,borderTopWidth:u,borderBottomWidth:f}=this.renderStyles,m=Math.PI/2;let _=this._getBorderRadius(),S=this.contentX-this.renderStyles.paddingLeft-y/2,L=this.contentY-this.renderStyles.paddingTop-u/2,w=t+i+r+(y+p)/2,x=e+s+o+(u+f)/2;const C=()=>{this.getCtx().moveTo(S,L+_),_&&this.getCtx().arc(S+_,L+_,_,2*m,3*m),this.getCtx().lineTo(S+w-_,L)},T=()=>{_&&this.getCtx().arc(S+w-_,L+_,_,3*m,4*m),this.getCtx().lineTo(S+w,L+x-_)},I=()=>{_&&this.getCtx().arc(S+w-_,L+x-_,_,0,m),this.getCtx().lineTo(S+_,L+x)},W=()=>{_&&this.getCtx().arc(S+_,L+x-_,_,m,2*m),this.getCtx().lineTo(S,L+_)};this.getCtx().lineCap=this.renderStyles.lineCap,this.getCtx().strokeStyle=this.renderStyles.borderColor,h&&"solid"!==h&&(Array.isArray(h)?this.getCtx().setLineDash(h):this.getCtx().setLineDash([5,5]));const b=t=>{this.getCtx().lineWidth=t,this.getCtx().stroke()};d&&l&&this._restore(()=>{this._path(()=>{C(),T(),I(),W()}),n(g)&&(this.getCtx().shadowOffsetX=g),n(c)&&(this.getCtx().shadowOffsetY=c),this.getCtx().shadowBlur=l,this.getCtx().shadowColor=d,this.getCtx().fillStyle=d,this.getCtx().fill()}),this._restore(()=>{this._path(()=>{S=this.contentX-this.renderStyles.paddingLeft-y/2,L=this.contentY-this.renderStyles.paddingTop-u/2,w=t+i+r+(y+p)/2,x=e+s+o+(u+f)/2,this.renderStyles.borderTopWidth&&(C(),b(this.renderStyles.borderTopWidth)),this.renderStyles.borderRightWidth&&(this.getCtx().moveTo(S+w-_,L),T(),b(this.renderStyles.borderRightWidth)),this.renderStyles.borderBottomWidth&&(this.getCtx().moveTo(S+w,L+x-_),I(),b(this.renderStyles.borderBottomWidth)),this.renderStyles.borderLeftWidth&&(this.getCtx().moveTo(S+_,L+x),W(),b(this.renderStyles.borderLeftWidth))})})}_clip(){if("hidden"!==this.renderStyles.overflow)return;const{contentWidth:t,contentHeight:e,paddingLeft:i,paddingTop:s,paddingRight:n,paddingBottom:h,shadowBlur:r,shadowColor:o,backgroundColor:l,borderLeftWidth:d,borderRightWidth:a,borderTopWidth:g,borderBottomWidth:c}=this.renderStyles,y=Math.PI/2;let p=this._getBorderRadius(),u=this.contentX-this.renderStyles.paddingLeft-d,f=this.contentY-this.renderStyles.paddingTop-g,m=t+i+n+d+a,_=e+s+h+g+c;const S=()=>{this.getCtx().moveTo(u,f+p),p&&this.getCtx().arc(u+p,f+p,p,2*y,3*y),this.getCtx().lineTo(u+m-p,f)},L=()=>{p&&this.getCtx().arc(u+m-p,f+p,p,3*y,4*y),this.getCtx().lineTo(u+m,f+_-p)},w=()=>{p&&this.getCtx().arc(u+m-p,f+_-p,p,0,y),this.getCtx().lineTo(u+p,f+_)},x=()=>{p&&this.getCtx().arc(u+p,f+_-p,p,y,2*y),this.getCtx().lineTo(u,f+p)};this._path(()=>{S(),L(),w(),x()}),this.getCtx().clip()}_getBorderRadius(){const{contentWidth:t,contentHeight:e}=this.renderStyles;let{borderRadius:i}=this.renderStyles;return 2*i>t&&(i=t/2),2*i>e&&(i=e/2),i<0&&(i=0),i}}class y extends g{constructor(t,e){super(t,e),this._layout=null,this._lines=[],this.children+=""}_getDefaultStyles(){return{...s.DEFAULT_STYLES,display:s.DISPLAY.INLINE_BLOCK,width:s.WIDTH.AUTO,textAlign:"left"}}_completeStyles(){super._completeStyles()}_completeWidth(){super._completeWidth(),"left"!==this.styles.textAlign&&this.parent&&!h(this.parent.styles.width)&&(this.styles.width="100%")}_initLayout(){super._initLayout()}_measureLayout(){return this._restore(()=>{this.getCtx().font=this._getFont();const{width:t}=this.getCtx().measureText(this.children);this._layout={width:t},this._layout.fontHeight=.9*this.renderStyles.fontSize,this._layout.height=this.renderStyles.lineHeight,this._calcLine()}),this._layout}_drawContent(){const{color:t,contentWidth:e,lineHeight:i,textAlign:n,textIndent:h}=this.renderStyles;let r=this.contentX;this.getCtx().fillStyle=t,this.getCtx().textAlign=n,this.getCtx().font=this._getFont(),n===s.TEXT_ALIGN.RIGHT?r=this.contentX+e:n===s.TEXT_ALIGN.CENTER&&(r=this.contentX+e/2);let o=r;this._lines.forEach((t,e)=>{o=0===e&&h?r+h:r,this.getCtx().fillText(t,o,this.contentY+(i+this._layout.fontHeight)/2+i*e)})}_getFont(){const{fontSize:t,fontWeight:e,fontFamily:i}=this.renderStyles;return`${e} ${t}px ${i}`}_calcLine(){if(!this.parent||!this.children)return;const{width:t,height:e}=this._layout,{contentWidth:i}=this.parent.renderStyles,{width:h}=this.parent.styles;if(n(i)&&i>=t||h===s.WIDTH.AUTO)this._lines=[this.children];else{this._lines=[];let t=1,e="",s=null;for(let n=0;ni){if(t>=this.renderStyles.maxLine){e=e.substring(0,e.length-2)+"...";break}this._lines.push(e),e="",t+=1}e+=this.children[n]}this._lines.push(e),this._layout.width=i,this._layout.height=this._lines.length*this.renderStyles.lineHeight}}}class p extends c{init(){super.init(),this._imageInfo={width:0,height:0,sx:0,sy:0,swidth:0,sheight:0,dx:0,dy:0,dwidth:0,dheight:0},this._image=null,this._loadImage()}_loadImage(){return new Promise((t,e)=>{var i,s;(i=this.options.attrs.src,s=this.getLayer().getCanvas(),new Promise((t,e)=>{let n=null;n=window?new Image:s.createImage(),n.src=i,n.onload=function(e){t({image:n,info:{width:e.target.width,height:e.target.height}})}})).then(({info:e,image:i})=>{this._imageInfo=e,this._image=i,t(),this._layoutImage(),this.getLayer().reflow(),this.getLayer().repaint(),this.options.on&&this.options.on.load&&this.options.on.load(this)})})}_drawContent(){if(!this._image)return;const{contentWidth:t,contentHeight:e}=this.renderStyles,{mode:i}=this.options.attrs,{sx:s,sy:n,swidth:h,sheight:r,dx:o,dy:l,dwidth:d,dheight:a,width:g,height:c}=this._imageInfo;"aspectFill"===i?this.getCtx().drawImage(this._image,s,n,h,r,this.contentX,this.contentY,t,e):"aspectFit"===i?this.getCtx().drawImage(this._image,0,0,g,c,o,l,d,a):this.getCtx().drawImage(this._image,this.contentX,this.contentY,t,e)}_layoutImage(){const{contentWidth:t,contentHeight:e}=this.renderStyles,{mode:i}=this.options.attrs,{width:s,height:n}=this.styles,{width:r,height:o}=this._imageInfo;let l=t,d=e;!h(s)&&h(n)?(l=t,d=f(l,r,o)):!h(n)&&h(s)?(d=e,l=u(d,r,o)):h(s)&&h(n)?(l=r,d=o):"aspectFill"===i?l/d>r/o?(this._imageInfo.swidth=r,this._imageInfo.sheight=f(r,l,d),this._imageInfo.sx=0,this._imageInfo.sy=(o-this._imageInfo.sheight)/2):(this._imageInfo.sheight=o,this._imageInfo.swidth=u(o,t,e),this._imageInfo.sy=0,this._imageInfo.sx=(r-this._imageInfo.swidth)/2):"aspectFit"===i?l/d>r/o?(this._imageInfo.dwidth=u(e,r,o),this._imageInfo.dheight=e,this._imageInfo.dy=this.contentY,this._imageInfo.dx=(t-this._imageInfo.dwidth)/2+this.contentX):(this._imageInfo.dheight=f(t,r,o),this._imageInfo.dwidth=t,this._imageInfo.dx=this.contentX,this._imageInfo.dy=(e-this._imageInfo.dheight)/2+this.contentY):(l=t,d=e),this._layout={width:l,height:d}}_measureLayout(){return this._layout?this._layout:{width:this.renderStyles.width,height:this.renderStyles.height}}}function u(t,e,i){return t/i*e}function f(t,e,i){return t/e*i}class m{constructor({simulateClick:t=!0}){this.clickList=[],this.touchstartList=[],this.touchmoveList=[],this.touchendList=[],this.touchStartEvent=null,this.simulateClick=t}clear(){this.clickList=[],this.touchstartList=[],this.touchmoveList=[],this.touchendList=[]}click(t,e){let i=new _({x:t,y:e,type:"click"});this._emit(i)}touchstart(t,e){let i=new _({x:t,y:e,type:"touchstart"});this.touchStartEvent=i,this._emit(i)}touchmove(t,e){let i=new _({x:t,y:e,type:"touchmove"});this._emit(i)}touchend(t,e){let i=new _({x:t,y:e,type:"touchend"});this._emit(i),this.checkClick(i)}_emit(t){let e=[];switch(t.type){case"click":e=this.clickList;break;case"touchstart":e=this.touchstartList;break;case"touchmove":e=this.touchmoveList;break;case"touchend":e=this.touchendList}for(let i=0;i=i.x&&e>=i.y&&t<=i.x+i.renderStyles.width&&e<=i.y+i.renderStyles.height}createEvent(t,e,i){}removeElement(t){this.clickList=this.clickList.filter(e=>e.element!==t),this.touchstartList=this.touchstartList.filter(e=>e.element!==t),this.touchmoveList=this.touchmoveList.filter(e=>e.element!==t),this.touchendList=this.touchendList.filter(e=>e.element!==t)}onClick(t,e){this.clickList.unshift({callback:t,element:e})}onTouchStart(t,e){this.touchstartList.unshift({callback:t,element:e})}onTouchMove(t,e){this.touchmoveList.unshift({callback:t,element:e})}onTouchEnd(t,e){this.touchendList.unshift({callback:t,element:e})}checkClick(t){if(this.touchStartEvent&&this.simulateClick){let{x:e,y:i}=this.touchStartEvent,{x:s,y:n}=t,h=n*n+s*s-(i*i+e*e);h<10&&h>-10&&this.click(s,n)}}}class _{constructor({x:t,y:e,type:i}){this.x=t,this.y=e,this.scrollX=t,this.scrollY=e,this.type=i,this.cancelBubble=!1,this.currentTarget=null}stopPropagation(){this.cancelBubble=!0}}class S{constructor(t,e){this.ctx=t,this.node=null,this.nodeList=[],this.p2cList=[],this.c2pList=[],this.renderList=[],this.options=e,this.eventManager=new m(e)}update(t,e){this.ctx=t,this.options=e,this.node.container=this.options}mountNode(t){this.node=t,this.node.root=this.node,this.node.layer=this,this.node.container=this.options,this.eventManager.clear(),this.initRender()}initRender(){this.connectChildren(this.node),this.p2cList=this.getP2CList(this.node),this.c2pList=function(t){var e=[];if(null!=t){var i=[];for(i.unshift(t);0!=i.length;){var s=i.shift();e.push(s._generateRender());for(var n=s._getChildren(),h=n.length-1;h>=0;h--)i.push(n[h]._generateRender())}}return e}(this.node).reverse(),this.initNode(),console.log(this.p2cList),this.flow(),this.repaint()}getP2CList(t){return function(t,e=!1){var i=[];if(null!=t){var s=[];for(s.unshift(t);0!=s.length;){var n=s.shift();i.push(n._generateRender());for(var h=n._getChildren(),r=0;r(e._setParent(t),e._setSibling(t._getChildren()[i-1],t._getChildren()[i+1]),this.connectChildren(e))).reduce((t,e)=>[...t,...e]);return[t._generateRender(),...e]}return[t._generateRender()]}initNode(t=this.node){l(t,t=>{t.init()})}flow(t=this.node){this.reflow()}initPaintList(){this.renderList=this.nodeList}reflow(t=this.node){for(let t=0;t{t._repaint(),t._afterPaint()}),this.ctx.draw&&this.ctx.draw()}getCanvas(){return this.options&&this.options.canvas}}class L extends c{constructor(t,e){return super(t,e),t.styles.overflow="hidden",this.type="scroll-view",this._scrollView=new c(t,[this]),this._scrollView.type="scroll-view-container",this._scrollView}_getDefaultStyles(){return{...s.DEFAULT_STYLES,direction:"y"}}init(){super.init(),this.addEventListener();const{height:t,width:e,direction:i}=this.styles;"y"===i?h(t)?console.error("scroll-view 必须设置明确的高度"):(this.styles.height="auto",this.renderStyles.height="auto"):"x"===i&&(h(e)?console.error("scroll-view 必须设置明确的宽度"):(this.styles.width="auto",this.renderStyles.width="auto"))}addEventListener(){this.currentScroll=0;let t=this.styles.direction,e=0,i=0,s=!1,n=0,h=0,r=null,o=1;this.getLayer().eventManager.onTouchStart(n=>{n.stopPropagation(),e=n[t],i=e,s=!0,clearInterval(r)},this._scrollView),this.getLayer().eventManager.onTouchMove(h=>{s&&(h.stopPropagation(),n=h[t]-e,this.scrollBy(n)&&(i=e,e=h[t]))},this._scrollView),this.getLayer().eventManager.onTouchEnd(e=>{s&&(s=!1,h=2*(e[t]-i),o=.02*-h,clearInterval(r),r=setInterval(()=>{this.scrollBy(h)||clearInterval(r),h+=o,h*h<=.05&&(h=0,clearInterval(r))},18))},this._scrollView)}_repaint(){const{direction:t}=this.renderStyles;"y"===t?this.getCtx().translate(0,this.currentScroll):this.getCtx().translate(this.currentScroll,0),super._repaint()}calcScrollBound(t){const{contentWidth:e,contentHeight:i}=this._scrollView.renderStyles,{width:s,height:n,direction:h}=this.renderStyles;if("y"===h){if(i-this.currentScroll-t>n)return!1;if(this.currentScroll+t>0)return!1}else{if(e-this.currentScroll-t>s)return!1;if(this.currentScroll+t>0)return!1}return!0}scrollBy(t){return!!this.calcScrollBound(t)&&(this.currentScroll+=t,this.getLayer().repaint(),!0)}scrollTo(t){}}const w={};function x(t,e){if(w[t])throw Error(`Already exist tag name [${t}] !`);w[t]=e}x("view",(t,e)=>new c(t,e)),x("text",(t,e)=>new y(t,e)),x("image",(t,e)=>new p(t,e)),x("scroll-view",(t,e)=>new L(t,e)),x("scrollview",(t,e)=>new L(t,e));return{createLayer:function(t,e){return new S(t,e)},createElement:function(t){return t((function t(e,i={},s=[]){let n=null,h=s;if(!w[e])throw Error(`Unknown tag name [${e}] !`);return n=w[e](i,h,t),n}))},component:x,View:c,Text:y,Image:p,Layer:S,ScrollView:L}})); diff --git a/example/basic.html b/example/basic.html index e5b0090..bd82ca5 100644 --- a/example/basic.html +++ b/example/basic.html @@ -77,13 +77,21 @@ layer = easyCanvas.createLayer(ctx, { dpr, width: w, height: h, debug: true }) const node = easyCanvas.createElement((h) => { - return h('view', { styles: { backgroundColor: '#fff', textAlign: 'center' } }, + return h('view', { styles: { backgroundColor: '#fff', textAlign: 'right' } }, [ - // drawListItem(h), - h('button', {}, 'button1'), - h('button', {}, 'button2'), - h('button', {}, 'button3'), - + drawListItem(h), + // h('button', {}, 'button1'), + // h('button', {}, 'button2'), + // h('button', {}, 'button3'), + // h('view', { + // styles: { + // display: 'flex' + // } + // }, [ + // h('view', { styles: { width: 50, backgroundColor: 'blue' } }, [h('button', {}, '100')]), + // h('view', { styles: { flex: 1, backgroundColor: 'red' } }, [h('button', {}, 'flex-1')]), + // h('view', { styles: { flex: 1, backgroundColor: 'green' } }, [h('button', {}, 'flex-1')]), + // ]) ] ) }) diff --git a/example/change-element.html b/example/change-element.html index ac31e7d..fe4b773 100644 --- a/example/change-element.html +++ b/example/change-element.html @@ -49,18 +49,18 @@ const deleteItem = () => node.removeChild(node.children[node.children.length - 1]) let dialog = null const toggleDialog = () => { - if(dialog){ + if (dialog) { node.removeChild(dialog) dialog = null - }else{ - dialog = node.appendChild(Dialog(h,{ - on:{ - click(){ + } else { + dialog = node.appendChild(Dialog(h, { + on: { + click() { toggleDialog() } }, - title:'提示', - content:'easy-canvas实现了在canvas中创建文档流,并且可以很轻松的支持组件化开发,并且没有第三方依赖,只要支持标准的canvas就可以使用,在实现基本功能的基础上添加了事件、scroll-view等支持。基础版支持小程序、web。' + title: '提示', + content: 'easy-canvas实现了在canvas中创建文档流,并且可以很轻松的支持组件化开发,并且没有第三方依赖,只要支持标准的canvas就可以使用,在实现基本功能的基础上添加了事件、scroll-view等支持。基础版支持小程序、web。' })) } } @@ -88,6 +88,15 @@ } } }, 'Delete'), + Dialog(h, { + on: { + click() { + toggleDialog() + } + }, + title: '提示', + content: 'easy-canvas实现了在canvas中创建文档流,并且可以很轻松的支持组件化开发,并且没有第三方依赖,只要支持标准的canvas就可以使用,在实现基本功能的基础上添加了事件、scroll-view等支持。基础版支持小程序、web。' + }) ]) }) diff --git a/example/draw.js b/example/draw.js index 539e794..29d7d2f 100644 --- a/example/draw.js +++ b/example/draw.js @@ -163,7 +163,7 @@ function drawButton(h, text = 'text', options = {}) { 'text', { styles: { - lineHeight: 20, + lineHeight: 16, color: options.color || '#fff', textAlign: 'center', fontSize: 11, @@ -225,7 +225,7 @@ function drawInlineBlock(h) { styles: { height: 20, backgroundColor: '#ff6c79', - borderRadius: 4, + borderRadius: 2, borderColor: '#fff', margin: 2, paddingLeft: 10, @@ -238,7 +238,7 @@ function drawInlineBlock(h) { 'text', { styles: { - lineHeight: 20, + lineHeight: 16, color: '#fff', fontSize: 11, }, @@ -305,10 +305,10 @@ function drawCard(h) { }), ]), ]), - h('view', {}, [ + h('view', { styles: { lineHeight: 30 } }, [ h( 'text', - { styles: { color: '#fff', fontSize: 12, lineHeight: 20 } }, + { styles: { color: '#fff', fontSize: 12, lineHeight: 20, verticalAlign: 'middle' } }, '最新收益0.00' ), h( @@ -317,18 +317,18 @@ function drawCard(h) { styles: { display: 'inline-block', marginLeft: 10, - marginTop: 3, height: 16, borderRadius: 8, paddingLeft: 5, paddingRight: 5, backgroundColor: '#666', + verticalAlign: 'middle' }, }, [ h( 'text', - { styles: { fontSize: 10, color: '#fff' } }, + { styles: { fontSize: 10, lineHeight: 16, color: '#fff' } }, '赠送权益' ), ] @@ -343,15 +343,16 @@ function drawCard(h) { paddingTop: 20, borderTopWidth: 0.5, borderColor: '#fff', - alignItems: 'top' + alignItems: 'flex-end', + color: 'yellow' }, }, [ - h('view', { styles: { flex: 1, color: '#fff' } }, [ - h('text', { styles: { color: '#fff' } }, '风险评测'), + h('view', { styles: { flex: 1 } }, [ + h('text', { styles: {} }, '风险评测'), h( 'text', - { styles: { color: '#fff' } }, + { styles: {} }, '风险评测风险评测风险评测' ), ]), @@ -364,8 +365,8 @@ function drawCard(h) { }, }, [ - h('text', { styles: { color: '#fff', textAlign: 'center', width: '100%' } }, '我的定投'), - h('text', { styles: { color: '#fff' } }, '风险评测'), + h('text', { styles: { textAlign: 'center', width: '100%' } }, '我的定投'), + h('text', { styles: {} }, '风险评测'), ] ), h( @@ -378,8 +379,8 @@ function drawCard(h) { }, }, [ - h('text', { styles: { color: '#fff', textAlign: 'center' } }, '优惠券'), - h('text', { styles: { color: '#fff' } }, '风险评测'), + h('text', { styles: { textAlign: 'center' } }, '优惠券'), + h('text', { styles: {} }, '风险评测'), ] ), ] @@ -432,7 +433,7 @@ function Dialog(h, options) { return h('view', { attrs: { className: 'dialog' }, styles: { position: 'absolute', top: 0, left: 0, width: window.innerWidth, height: window.innerHeight, backgroundColor: 'rgba(0,0,0,0.5)', - display: 'flex', alignItems: 'center', justifyContent: 'center' + display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-start' } }, [ h('view', { diff --git a/lib/element.js b/lib/element.js index 4dcd0de..fc50cd0 100644 --- a/lib/element.js +++ b/lib/element.js @@ -2,6 +2,7 @@ import STYLES from './constants' import pxUtil from './px' import { isExact, walk, isOuter, parseOuter, walkParent, isEndNode, isAuto } from './utils' import Line from './line' +import FlexBox from './flex-box' /** * Element类实现盒模型以及定位,不具备绘制 @@ -67,7 +68,7 @@ export default class Element { } _initStyles() { - this.styles = Object.assign({}, this._getDefaultStyles(), this._getParentStyles(), this.options.styles || {}) + this.styles = Object.assign({}, this._getDefaultStyles(), this._getParentStyles(this.options.styles), this.options.styles || {}) this._completeStyles() @@ -91,22 +92,24 @@ export default class Element { renderStyles.height = parseOuter(renderStyles.height) * parentHeight } - - if (!renderStyles.width) renderStyles.width = 0 if (!renderStyles.height) renderStyles.height = 0 // 初始化contentWidth - renderStyles.contentWidth = renderStyles.width - renderStyles.paddingLeft - renderStyles.paddingRight - renderStyles.marginLeft - renderStyles.marginRight - renderStyles.borderWidth - renderStyles.contentHeight = renderStyles.height - renderStyles.paddingTop - renderStyles.paddingBottom - renderStyles.marginTop - renderStyles.marginBottom - renderStyles.borderWidth + renderStyles.contentWidth = renderStyles.width - renderStyles.paddingLeft - renderStyles.paddingRight - renderStyles.marginLeft - renderStyles.marginRight - this._getTotalBorderWidth(renderStyles) + renderStyles.contentHeight = renderStyles.height - renderStyles.paddingTop - renderStyles.paddingBottom - renderStyles.marginTop - renderStyles.marginBottom - this._getTotalBorderWidth(renderStyles) this.renderStyles = renderStyles + + if (this._InFlexBox()) { + this._bindFlexBox() + } } /** * 需要继承的styles放在这里 */ - _getParentStyles() { + _getParentStyles(curStyles) { let { textAlign, lineHeight, fontSize, color, fontFamily, alignItems } = this.parent && this.parent.styles || {} let extendStyles = {} // if (textAlign) extendStyles.textAlign = textAlign @@ -114,6 +117,7 @@ export default class Element { if (fontSize) extendStyles.fontSize = fontSize if (color) extendStyles.color = color if (fontFamily) extendStyles.fontFamily = fontFamily + if (alignItems && !curStyles.alignSelf) extendStyles.alignSelf = alignItems return extendStyles } @@ -302,12 +306,14 @@ export default class Element { if (display === STYLES.DISPLAY.INLINE_BLOCK) { // 如果是inline-block 这里仅计算高度 this._bindLine() + } else if (this._InFlexBox()) { + this.line.refreshWidthHeight(this) } } _initPosition() { const { contentX, contentY, contentWidth, contentHeight } = this._getContainerLayout() - const { paddingLeft, paddingTop, borderWidth, marginLeft, marginTop } = this.renderStyles + const { paddingLeft, paddingTop, borderLeftWidth, borderTopWidth, marginLeft, marginTop } = this.renderStyles // 初始化ctx位置 if (!this.isInFlow()) { // 不在文档流中 @@ -324,7 +330,7 @@ export default class Element { this.x = contentX + contentWidth - right - width } } else if (this._InFlexBox()) { - + this.line.refreshElementPosition(this) } else if (this.renderStyles.display === STYLES.DISPLAY.INLINE_BLOCK) { // inline-block到line里计算 // this._bindLine() @@ -333,8 +339,8 @@ export default class Element { this.x = contentX this.y = this._getPreLayout().y + this._getPreLayout().height } - this.contentX = this.x + paddingLeft + borderWidth + marginLeft - this.contentY = this.y + paddingTop + borderWidth + marginTop + this.contentX = this.x + paddingLeft + borderLeftWidth + marginLeft + this.contentY = this.y + paddingTop + borderTopWidth + marginTop } _InFlexBox() { @@ -345,8 +351,18 @@ export default class Element { // 父元素根据子元素撑开content后,再计算width _refreshLayoutWithContent() { - this.renderStyles.height = this.renderStyles.contentHeight + this.renderStyles.paddingTop + this.renderStyles.paddingBottom + this.renderStyles.marginTop + this.renderStyles.marginBottom + this.renderStyles.borderWidth - this.renderStyles.width = this.renderStyles.contentWidth + this.renderStyles.paddingLeft + this.renderStyles.paddingRight + this.renderStyles.marginLeft + this.renderStyles.marginRight + this.renderStyles.borderWidth + this.renderStyles.height = this.renderStyles.contentHeight + this.renderStyles.paddingTop + this.renderStyles.paddingBottom + this.renderStyles.marginTop + this.renderStyles.marginBottom + this._getTotalBorderWidth() + this.renderStyles.width = this.renderStyles.contentWidth + this.renderStyles.paddingLeft + this.renderStyles.paddingRight + this.renderStyles.marginLeft + this.renderStyles.marginRight + this._getTotalBorderWidth() + } + + // 父元素根据子元素撑开content后,再计算width + _refreshContentWithLayout() { + this.renderStyles.contentHeight = this.renderStyles.height - this.renderStyles.paddingTop - this.renderStyles.paddingBottom - this.renderStyles.marginTop - this.renderStyles.marginBottom - this._getTotalBorderWidth() + this.renderStyles.contentWidth = this.renderStyles.width - this.renderStyles.paddingLeft - this.renderStyles.paddingRight - this.renderStyles.marginLeft - this.renderStyles.marginRight - this._getTotalBorderWidth() + } + + _getTotalBorderWidth(renderStyles = this.renderStyles) { + return renderStyles.borderLeftWidth + renderStyles.borderRightWidth + renderStyles.borderTopWidth + renderStyles.borderBottomWidth } _bindLine() { @@ -358,79 +374,13 @@ export default class Element { } } - /** - * 计算子元素高度,撑开父元素 - */ - _calcContentHeightWidthChildren() { - // todo 没有考虑inline-block - let complete = true - let lineHeight = 0 - const heightArr = [] - this._getChildrenInFlow().forEach(child => { - // 如果还有没计算完成的,这里可以去掉了,全面通过是否有下一个元素判断好了 - if (!typeof child.renderStyles.height === 'number') { - complete = false - } - // 如果是第一个元素需要纳入计算 - if (child._needNewLine()) { - // 从一行到下一个新一行 - heightArr.push(lineHeight) - lineHeight = child.renderStyles.height - - } else { - // 如果是同一行,取最大的 - if (child.renderStyles.height > lineHeight) { - lineHeight = child.renderStyles.height - } - } - - if (!child.next) { - // 如果没有下一个了 - heightArr.push(lineHeight) - lineHeight = 0 - } - }) - - return complete ? heightArr.reduce((sum, val) => sum + (val >= 0 ? val : 0)) : this.renderStyles.height - - } - - /** - * 是否需要新起一行 - */ - _needNewLine() { - const { display } = this.renderStyles - const { whiteSpace } = this.parent && this.parent.renderStyles || {} - if (!this.pre) return true - // flex容器内 - if (this.parent && this.parent.renderStyles.display === STYLES.DISPLAY.FLEX && this.pre && this.parent.renderStyles.flexDirection === STYLES.FLEX_DIRECTION.ROW) { - return false - } - - // block等 - if (display === STYLES.DISPLAY.BLOCK || display === STYLES.DISPLAY.FLEX) { - return true - } - - // 到这里都是inline-block或者inline了 - if (whiteSpace === 'nowrap') return false - if (this.pre) { - let { width } = this.renderStyles - if (width === STYLES.WIDTH.AUTO) width = 0 - const { display, width: preWidth } = this.pre.renderStyles - const { width: containerWidth, x: containerX } = this._getContainerLayout() - if (display === STYLES.DISPLAY.BLOCK || display === STYLES.DISPLAY.FLEX) { - return true - } else if ((preWidth + this.pre.x + width) > (containerX + containerWidth)) { - // 这里将当前宽度等于上一个的宽度了 因为这里宽度还是0,暂时还没有好的解决方案 - // 如果inlineblock顶到右边,换行 - return true - } - + _bindFlexBox() { + if (this.pre && this.pre.line) { + this.pre.line.add(this) + } else { + // 新行 + new FlexBox().bind(this) } - - return false - } _getContainerLayout() { @@ -510,101 +460,24 @@ export default class Element { let width = 0 // 需要考虑原本的宽度 let height = 0 this._getChildrenInFlow().forEach(child => { - if (child.renderStyles.width > width) { + if (child.line) { + if (child.line.start === child) { + if (child.line.width > width) { + width = child.line.width + } + height += child.line.height + } + } else if (child.renderStyles.width > width) { width = child.renderStyles.width + height += child.renderStyles.height + } else { + height += child.renderStyles.height } - height += child.renderStyles.height }) return { width, height } } - _px(num) { - // if (num && isExact(num)) { - // return num / this.root.container.dpr - // } - return num - } - - // 原理 统一从左边往右移动 必须在正常排列完成后再次进行 - _patchInlineBlockAlign() { - const { textAlign, contentWidth, lineHeight } = this.renderStyles - const children = this._getChildrenInFlow() - if (!children.length || children[0].renderStyles.display !== 'inline-block') return - let rightOffset = 0 - let curLineHeight = 0 - const translateX = (element) => { - let offsetY = 0 - if (element.renderStyles.verticalAlign === 'bottom') { - offsetY = curLineHeight - element.renderStyles.height - } else if (element.renderStyles.verticalAlign === 'middle') { - offsetY = (curLineHeight - element.renderStyles.height) / 2 - } else { - offsetY = 0 - } - walk(element, (child) => { - if (child.isInFlow()) { - child.x += rightOffset - child.contentX += rightOffset - child.y += offsetY - child.contentY += offsetY - } - }) - - } - const refreshX = (element) => { - curLineHeight = getLineHeight(element) - rightOffset = this.contentX + contentWidth - (element.x + element.renderStyles.width) - if (textAlign === 'center') rightOffset = rightOffset / 2 - if (textAlign === 'left' || !textAlign) rightOffset = 0 - } - let cur = null - for (let i = children.length - 1; i >= 0; i--) { - cur = children[i] - if (!cur.next) { - // 最后一个 只有一个也会走这里 - refreshX(cur) - translateX(cur) - } else if (!cur.pre) { - // 第一个 - translateX(cur) - } else if (cur.next._needNewLine()) { - refreshX(cur) - translateX(cur) - } else { - translateX(cur) - } - } - } - - _patchFlex() { - const children = this._getChildrenInFlow() - const { contentHeight, contentWidth, alignItems, justifyContent } = this.renderStyles - if (this.renderStyles.display !== 'flex' || !children.length) return - let offsetX = 0 - let offsetY = 0 - if (justifyContent === 'flex-end') { - offsetX = (this.contentX + contentWidth) - (children[children.length - 1].renderStyles.width + children[children.length - 1].x) - } else if (justifyContent === 'center') { - offsetX = ((this.contentX + contentWidth) - (children[children.length - 1].renderStyles.width + children[children.length - 1].x)) / 2 - } - for (let i = children.length - 1; i >= 0; i--) { - if (alignItems === 'flex-end') { - offsetY = contentHeight - children[i].renderStyles.height - } else if (alignItems === 'center') { - offsetY = (contentHeight - children[i].renderStyles.height) / 2 - } else { - offsetY = 0 - } - walk(children[i], (el) => { - el.y += offsetY - el.contentY += offsetY - el.x += offsetX - el.contentX += offsetX - }) - } - } - // 获取元素,只会找该元素子级 getElementBy(key, value) { let match = [] @@ -691,52 +564,3 @@ export default class Element { } } - -// 根据宽高获取内容高度 这里改变了原有宽高 -function getContentLayout(element) { - let width = element.renderStyles.width - let height = element.renderStyles.height - if (!isExact(width)) width = 0 - if (!isExact(height)) height = 0 - // 最大宽度判断 - if (element.renderStyles.minWidth && width < element.renderStyles.minWidth) width = element.renderStyles.width = element.renderStyles.minWidth - if (element.renderStyles.maxWidth && width > element.renderStyles.maxWidth) width = element.renderStyles.width = element.renderStyles.maxWidth - if (element.renderStyles.maxHeight && height > element.renderStyles.maxHeight) height = element.renderStyles.height = element.renderStyles.maxHeight - if (element.renderStyles.minHeight && height > element.renderStyles.minHeight) height = element.renderStyles.height = element.renderStyles.minHeight - return { - contentWidth: width - element.renderStyles.paddingLeft - element.renderStyles.paddingRight - element.renderStyles.marginLeft - element.renderStyles.marginRight, - contentHeight: height - element.renderStyles.paddingTop - element.renderStyles.paddingBottom - element.renderStyles.marginTop - element.renderStyles.marginBottom, - contentX: element.x + element.renderStyles.paddingLeft + element.renderStyles.marginLeft, - contentY: element.y + element.renderStyles.paddingTop + element.renderStyles.marginTop, - width, - height - } -} - - -function getLineHeight(el) { - if (el.renderStyles.display === 'block' || el.renderStyles.display === 'flex') return el.renderStyles.height - // right - let max = 0 - let cur = el - while (cur && !cur._needNewLine()) { - if (cur.renderStyles.height > max && cur.isInFlow()) { - max = cur.renderStyles.height - } - cur = cur.next - } - // left - cur = el - while (cur && !cur._needNewLine()) { - if (cur.renderStyles.height > max && cur.isInFlow()) { - max = cur.renderStyles.height - } - cur = cur.pre - } - - // 最后肯定剩下一个头 - if (cur.renderStyles.height > max) { - max = cur.renderStyles.height - } - return max -} diff --git a/lib/flex-box.js b/lib/flex-box.js new file mode 100644 index 0000000..03e99e4 --- /dev/null +++ b/lib/flex-box.js @@ -0,0 +1,59 @@ +import Line from './line' +import { isExact } from './utils' + +export default class FlexBox extends Line { + constructor() { + super() + this.exactValue = 0 + this.flexTotal = 0 + } + + closeLine() { + super.closeLine() + this.calcFlex() + } + + add(el) { + if (isExact(el.styles.flex)) { + this.flexTotal += el.styles.flex + } else if (isExact(el.styles.width)) { + this.exactValue += el.styles.width + } + super.add(el) + } + + initHeight() { + this.height = 0 + } + + calcFlex() { + const { contentWidth: containerWidth } = this.container.renderStyles + this.elements.forEach(child => { + if (isExact(child.styles.flex)) { + child.renderStyles.width = (child.styles.flex / this.flexTotal) * (containerWidth - this.exactValue) + child._refreshContentWithLayout() + } + }) + } + + refreshXAlign() { + if (!this.end.parent) return + let offsetX = this.outerWidth - this.width + if (this.end.parent.renderStyles.justifyContent === 'center') { + offsetX = offsetX / 2 + } else if (this.end.parent.renderStyles.justifyContent === 'flex-start') { + offsetX = 0 + } + this.offsetX = offsetX + } + + getOffsetY(el) { + if (el.renderStyles.alignSelf === 'flex-end') { + return (this.container.renderStyles.contentHeight - el.renderStyles.height) + } else if (el.renderStyles.alignSelf === 'center') { + return (this.container.renderStyles.contentHeight - el.renderStyles.height) / 2 + } else { + return 0 + } + } +} diff --git a/lib/image.js b/lib/image.js index a46df5f..2d9de11 100644 --- a/lib/image.js +++ b/lib/image.js @@ -10,14 +10,14 @@ export default class $Image extends View { this._imageInfo = { width: 0, height: 0, - sx:0, - sy:0, - swidth:0, - sheight:0, - dx:0, - dy:0, - dwidth:0, - dheight:0 + sx: 0, + sy: 0, + swidth: 0, + sheight: 0, + dx: 0, + dy: 0, + dwidth: 0, + dheight: 0 } this._image = null this._loadImage() @@ -34,11 +34,11 @@ export default class $Image extends View { this._layoutImage() // // 重新布局绘制 - // this.getLayer().reflow() - // this.getLayer().repaint() + this.getLayer().reflow() + this.getLayer().repaint() // call load callback - if(this.options.on && this.options.on.load){ + if (this.options.on && this.options.on.load) { this.options.on.load(this) } }) @@ -48,13 +48,13 @@ export default class $Image extends View { _drawContent() { if (!this._image) return const { contentWidth, contentHeight } = this.renderStyles - const {mode} = this.options.attrs - const {sx,sy,swidth,sheight,dx,dy,dwidth,dheight,width:imageW,height:imageH} = this._imageInfo - if(mode === 'aspectFill'){ - this.getCtx().drawImage(this._image,sx,sy,swidth,sheight, this.contentX, this.contentY, contentWidth, contentHeight) - }else if(mode === 'aspectFit'){ - this.getCtx().drawImage(this._image,0,0,imageW,imageH,dx,dy,dwidth,dheight) - }else{ + const { mode } = this.options.attrs + const { sx, sy, swidth, sheight, dx, dy, dwidth, dheight, width: imageW, height: imageH } = this._imageInfo + if (mode === 'aspectFill') { + this.getCtx().drawImage(this._image, sx, sy, swidth, sheight, this.contentX, this.contentY, contentWidth, contentHeight) + } else if (mode === 'aspectFit') { + this.getCtx().drawImage(this._image, 0, 0, imageW, imageH, dx, dy, dwidth, dheight) + } else { this.getCtx().drawImage(this._image, this.contentX, this.contentY, contentWidth, contentHeight) } } @@ -62,9 +62,9 @@ export default class $Image extends View { // 计算图片布局 _layoutImage() { const { contentWidth, contentHeight } = this.renderStyles - const {mode} = this.options.attrs + const { mode } = this.options.attrs const { width, height } = this.styles - const {width:imageW,height:imageH} = this._imageInfo + const { width: imageW, height: imageH } = this._imageInfo // 根据用户设置判断图片宽高,目前支持widthfix、heightfix、平铺 let w = contentWidth let h = contentHeight @@ -80,45 +80,45 @@ export default class $Image extends View { // auto w = imageW h = imageH - }else if(mode === 'aspectFill'){ + } else if (mode === 'aspectFill') { // 填充 - if((w/h) > (imageW/imageH)){ + if ((w / h) > (imageW / imageH)) { this._imageInfo.swidth = imageW - this._imageInfo.sheight = getHeightByWidth(imageW,w,h) + this._imageInfo.sheight = getHeightByWidth(imageW, w, h) this._imageInfo.sx = 0 - this._imageInfo.sy = (imageH - this._imageInfo.sheight)/2 - }else{ + this._imageInfo.sy = (imageH - this._imageInfo.sheight) / 2 + } else { this._imageInfo.sheight = imageH - this._imageInfo.swidth = getWidthByHeight(imageH,contentWidth,contentHeight) + this._imageInfo.swidth = getWidthByHeight(imageH, contentWidth, contentHeight) this._imageInfo.sy = 0 - this._imageInfo.sx = (imageW - this._imageInfo.swidth)/2 + this._imageInfo.sx = (imageW - this._imageInfo.swidth) / 2 } - }else if(mode === 'aspectFit'){ - if((w/h) > (imageW/imageH)){ - this._imageInfo.dwidth = getWidthByHeight(contentHeight,imageW,imageH) + } else if (mode === 'aspectFit') { + if ((w / h) > (imageW / imageH)) { + this._imageInfo.dwidth = getWidthByHeight(contentHeight, imageW, imageH) this._imageInfo.dheight = contentHeight this._imageInfo.dy = this.contentY - this._imageInfo.dx = (contentWidth - this._imageInfo.dwidth)/2 + this.contentX - }else{ - this._imageInfo.dheight = getHeightByWidth(contentWidth,imageW,imageH) + this._imageInfo.dx = (contentWidth - this._imageInfo.dwidth) / 2 + this.contentX + } else { + this._imageInfo.dheight = getHeightByWidth(contentWidth, imageW, imageH) this._imageInfo.dwidth = contentWidth this._imageInfo.dx = this.contentX - this._imageInfo.dy = (contentHeight - this._imageInfo.dheight)/2 + this.contentY + this._imageInfo.dy = (contentHeight - this._imageInfo.dheight) / 2 + this.contentY } - }else { + } else { w = contentWidth h = contentHeight } - this._layout = {width:w,h:height} + this._layout = { width: w, height: h } } - _measureLayout(){ - if(this._layout){ + _measureLayout() { + if (this._layout) { return this._layout - }else{ + } else { return { - width:this.renderStyles.width, - height:this.renderStyles.height + width: this.renderStyles.width, + height: this.renderStyles.height } } } diff --git a/lib/layer.js b/lib/layer.js index 058639e..c7e78d0 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -35,7 +35,6 @@ export default class Layer { // this.initPaintList() this.initNode() console.log(this.p2cList) - console.log(this.c2pList) this.flow() diff --git a/lib/line.js b/lib/line.js index e7b03df..57f8032 100644 --- a/lib/line.js +++ b/lib/line.js @@ -18,13 +18,17 @@ export default class Line { bind(el) { this.container = el.parent - this.height = el.parent && el.parent.renderStyles.lineHeight || 0 + this.initHeight(el) this.outerWidth = el.parent && isAuto(el.parent.styles.width) ? Infinity : el.parent.renderStyles.contentWidth this.start = el this.add(el) } + initHeight(el) { + this.height = el.parent && el.parent.renderStyles.lineHeight || 0 + } + initLayout(el) { this.right = el._getContainerLayout().contentX this.y = this.getPreLineBottom(el) @@ -36,11 +40,23 @@ export default class Line { } // 刷新位置,首先以左边计算 el.x = this.right + this.offsetX - el.y = this.y + (this.height - el.renderStyles.contentHeight) / 2 + el.y = this.y + this.getOffsetY(el) + // + (this.height - el.renderStyles.height) / 2 this.right += el.renderStyles.width } + getOffsetY(el) { + if (el.renderStyles.verticalAlign === 'bottom') { + return (this.height - el.renderStyles.height) + } else if (el.renderStyles.verticalAlign === 'middle') { + return (this.height - el.renderStyles.height) / 2 + } else { + return 0 + } + } + + add(el) { this.elements.push(el) el.line = this diff --git a/lib/text.js b/lib/text.js index 24b59dd..73a37d9 100644 --- a/lib/text.js +++ b/lib/text.js @@ -32,7 +32,7 @@ export default class Text extends Element { } _initLayout() { - + super._initLayout() } @@ -43,7 +43,7 @@ export default class Text extends Element { this._layout = { width } // 微信 夸克 有兼容性问题 // this._layout.fontHeight = this._layout.actualBoundingBoxAscent || this.renderStyles.fontSize - this._layout.fontHeight = this.renderStyles.fontSize + this._layout.fontHeight = this.renderStyles.fontSize * 0.9 this._layout.height = this.renderStyles.lineHeight this._calcLine() }) @@ -69,7 +69,7 @@ export default class Text extends Element { } else { _x = x } - this.getCtx().fillText(line, _x, (this.contentY + this._layout.fontHeight + ((lineHeight - this._layout.fontHeight) / 2) + lineHeight * index) - 1) + this.getCtx().fillText(line, _x, (this.contentY + ((lineHeight + this._layout.fontHeight) / 2) + lineHeight * index)) }) } diff --git a/lib/view.js b/lib/view.js index ca1ac6b..e022f7d 100644 --- a/lib/view.js +++ b/lib/view.js @@ -82,22 +82,22 @@ export default class View extends Element { this.getCtx().fillStyle = backgroundColor this.getCtx().fillRect(this.contentX - paddingLeft, this.contentY - paddingTop, contentWidth + paddingLeft + paddingRight, contentHeight + paddingTop + paddingBottom) } + + // for debug + if (this.getLayer().options && this.getLayer().options.debug) { + this.getCtx().strokeStyle = 'green' + this.getCtx().strokeRect(this.contentX, this.contentY, this.renderStyles.contentWidth, this.renderStyles.contentHeight) + // ctx.strokeStyle = '#fff' + // ctx.strokeText(`${parseInt(this.contentX)} ${parseInt(this.contentY)} ${contentWidth} ${contentHeight}`, this.contentX + 100, this.contentY + 10) + + // + } } _drawBox() { this._drawRadiusBorder() -// for debug -if (this.getLayer().options && this.getLayer().options.debug) { - this.getCtx().strokeStyle = 'green' - this.getCtx().strokeRect(this.x, this.y, this.renderStyles.width, this.renderStyles.height) - // ctx.strokeStyle = '#fff' - // ctx.strokeText(`${parseInt(this.contentX)} ${parseInt(this.contentY)} ${contentWidth} ${contentHeight}`, this.contentX + 100, this.contentY + 10) - - // -} - } _drawRadiusBorder() {