From f21ccaadc124eea009a2f23012ca813398c37372 Mon Sep 17 00:00:00 2001 From: jinfeiyang <1912144808@qq.com> Date: Sat, 5 Sep 2020 19:18:08 +0800 Subject: [PATCH] feat: update version --- build/rollup.config.js | 2 +- dist/easy-canvas.min.js | 1935 ++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 3 files changed, 1936 insertions(+), 3 deletions(-) diff --git a/build/rollup.config.js b/build/rollup.config.js index bfe7079..f4762d3 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 ad2480d..8cb5fec 100644 --- a/dist/easy-canvas.min.js +++ b/dist/easy-canvas.min.js @@ -1 +1,1934 @@ -!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.getLayer().mountNode(this.root),t}prependChild(t){if(!t instanceof g)throw Error("Unknown Element type");return this.children.unshift(t),this.getLayer().mountNode(this.root),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().mountNode(this.root)}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.getLayer().mountNode(this.root)}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.getLayer().mountNode(this.root)}}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){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=0;h(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}})); +(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() + } + + function breadthFirstSearch(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 = 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 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.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); + } + + refreshElementPosition(el) { + if (this.start === el) { + this.initLayout(el); + } + // 刷新位置,首先以左边计算 + el.x = this.right + this.offsetX; + 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; + 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; + } + } + + 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 + } + } + } + + /** + * 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.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 - 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(curStyles) { + 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; + if (alignItems && !curStyles.alignSelf) extendStyles.alignSelf = alignItems; + 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(); + } else if (this._InFlexBox()) { + this.line.refreshWidthHeight(this); + } + } + + _initPosition() { + const { contentX, contentY, contentWidth, contentHeight } = this._getContainerLayout(); + const { paddingLeft, paddingTop, borderLeftWidth, borderTopWidth, 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()) { + this.line.refreshElementPosition(this); + } 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 + borderLeftWidth + marginLeft; + this.contentY = this.y + paddingTop + borderTopWidth + 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._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() { + if (this.pre && this.pre.line && this.pre.line.canIEnter(this)) { + this.pre.line.add(this); + } else { + // 新行 + new Line().bind(this); + } + } + + _bindFlexBox() { + if (this.pre && this.pre.line) { + this.pre.line.add(this); + } else { + // 新行 + new FlexBox().bind(this); + } + } + + _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.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; + } + }); + + return { width, height } + } + + // 获取元素,只会找该元素子级 + 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.getLayer().mountNode(this.root); + return element + } + + // + prependChild(element) { + if (!element instanceof Element) throw Error('Unknown Element type') + this.children.unshift(element); + this.getLayer().mountNode(this.root); + 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().mountNode(this.root); + } + + 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.getLayer().mountNode(this.root); + } + + 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.getLayer().mountNode(this.root); + } + + } + + 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); + } + + // 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(); + + } + + _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 * 0.9; + 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 + ((lineHeight + this._layout.fontHeight) / 2) + lineHeight * index)); + }); + } + + _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, height: h }; + } + + _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); + + + 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 + } + + } + + 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; + +}))); diff --git a/package.json b/package.json index a0cf2ec..cdc6f94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "easy-canvas-layout", - "version": "0.0.6", + "version": "0.0.7", "description": "A canvas tool to help easy layout in canvas.", "main": "./lib/index.js", "scripts": {