@@ -59,6 +59,23 @@ const VUE_RUNTIME_HELPERS = {
5959 _withDirectives : withDirectives ,
6060}
6161
62+ const CANVAS_NODE_TYPE = 'dimina-canvas-node'
63+ const TYPED_ARRAY_CTORS = {
64+ Int8Array,
65+ Uint8Array,
66+ Uint8ClampedArray,
67+ Int16Array,
68+ Uint16Array,
69+ Int32Array,
70+ Uint32Array,
71+ Float32Array,
72+ Float64Array,
73+ }
74+
75+ function isCanvasElement ( element ) {
76+ return element ?. tagName ?. toLowerCase ( ) === 'canvas'
77+ }
78+
6279class Runtime {
6380 constructor ( ) {
6481 this . app = null
@@ -69,6 +86,9 @@ class Runtime {
6986 this . initializedModules = new Set ( )
7087 this . preInitUpdates = new Map ( )
7188 this . intersectionObservers = new Map ( )
89+ this . canvasNodes = new Map ( )
90+ this . canvasResources = new Map ( )
91+ this . canvasRafIds = new Map ( )
7292 // 追踪"mC 已发出但 service 侧 created 尚未完成"的组件 setup
7393 // key: moduleId, value: Promise(created 完成时 resolve)
7494 this . _pendingSetups = new Map ( )
@@ -711,6 +731,209 @@ class Runtime {
711731 return true
712732 }
713733
734+ getCanvasNodeId ( canvas ) {
735+ if ( ! canvas . __diminaCanvasNodeId ) {
736+ Object . defineProperty ( canvas , '__diminaCanvasNodeId' , {
737+ value : `canvas_${ uuid ( ) } ` ,
738+ configurable : true ,
739+ } )
740+ }
741+ return canvas . __diminaCanvasNodeId
742+ }
743+
744+ registerCanvasNode ( canvas , type = canvas . getAttribute ?. ( 'type' ) || '2d' ) {
745+ const nodeId = this . getCanvasNodeId ( canvas )
746+ const isNewNode = ! this . canvasNodes . has ( nodeId )
747+ const rect = canvas . getBoundingClientRect ?. ( )
748+ const width = Math . round ( rect ?. width || 0 )
749+ const height = Math . round ( rect ?. height || 0 )
750+ if ( isNewNode && width > 0 && height > 0 ) {
751+ if ( canvas . width !== width ) {
752+ canvas . width = width
753+ }
754+ if ( canvas . height !== height ) {
755+ canvas . height = height
756+ }
757+ }
758+
759+ if ( isNewNode ) {
760+ this . canvasNodes . set ( nodeId , {
761+ canvas,
762+ contexts : new Map ( ) ,
763+ } )
764+ }
765+ return {
766+ __diminaNodeType : CANVAS_NODE_TYPE ,
767+ nodeId,
768+ type,
769+ width : canvas . width || width || 300 ,
770+ height : canvas . height || height || 150 ,
771+ }
772+ }
773+
774+ createOffscreenCanvas ( { params } ) {
775+ const { nodeId, width = 300 , height = 150 , type = '2d' } = params
776+ const canvas = document . createElement ( 'canvas' )
777+ canvas . width = width
778+ canvas . height = height
779+ this . canvasNodes . set ( nodeId , {
780+ canvas,
781+ type,
782+ contexts : new Map ( ) ,
783+ } )
784+ }
785+
786+ resolveCanvasArg ( value ) {
787+ if ( value === null || value === undefined ) {
788+ return value
789+ }
790+
791+ if ( Array . isArray ( value ) ) {
792+ return value . map ( item => this . resolveCanvasArg ( item ) )
793+ }
794+
795+ if ( typeof value !== 'object' ) {
796+ return value
797+ }
798+
799+ if ( value . __canvasResourceId ) {
800+ return this . canvasResources . get ( value . __canvasResourceId )
801+ }
802+
803+ if ( value . __canvasNodeId ) {
804+ return this . canvasNodes . get ( value . __canvasNodeId ) ?. canvas
805+ }
806+
807+ if ( value . __canvasTypedArray ) {
808+ const Ctor = TYPED_ARRAY_CTORS [ value . __canvasTypedArray ]
809+ if ( Ctor ) {
810+ return new Ctor ( value . data || [ ] )
811+ }
812+ if ( value . __canvasTypedArray === 'DataView' ) {
813+ return new DataView ( new Uint8Array ( value . data || [ ] ) . buffer )
814+ }
815+ }
816+
817+ if ( value . __canvasArrayBuffer ) {
818+ return new Uint8Array ( value . data || [ ] ) . buffer
819+ }
820+
821+ const result = { }
822+ for ( const [ key , item ] of Object . entries ( value ) ) {
823+ result [ key ] = this . resolveCanvasArg ( item )
824+ }
825+ return result
826+ }
827+
828+ getCanvasResource ( id ) {
829+ return this . canvasResources . get ( id )
830+ }
831+
832+ setCanvasResource ( id , value ) {
833+ if ( id ) {
834+ this . canvasResources . set ( id , value )
835+ }
836+ }
837+
838+ getCanvasImage ( imageId ) {
839+ let image = this . getCanvasResource ( imageId )
840+ if ( ! image ) {
841+ image = new Image ( )
842+ this . setCanvasResource ( imageId , image )
843+ }
844+ return image
845+ }
846+
847+ executeCanvasOperation ( node , operation , bridgeId ) {
848+ switch ( operation . op ) {
849+ case 'setCanvasProperty' :
850+ node . canvas [ operation . prop ] = operation . value
851+ break
852+ case 'getContext' : {
853+ const context = node . canvas . getContext ( operation . contextType , this . resolveCanvasArg ( operation . attributes ) )
854+ node . contexts . set ( operation . contextId , context )
855+ this . setCanvasResource ( operation . contextId , context )
856+ break
857+ }
858+ case 'contextSetProperty' : {
859+ const context = this . getCanvasResource ( operation . contextId )
860+ if ( context ) {
861+ context [ operation . prop ] = this . resolveCanvasArg ( operation . value )
862+ }
863+ break
864+ }
865+ case 'contextCall' : {
866+ const context = this . getCanvasResource ( operation . contextId )
867+ const method = context ?. [ operation . method ]
868+ if ( typeof method === 'function' ) {
869+ const result = method . apply ( context , ( operation . args || [ ] ) . map ( arg => this . resolveCanvasArg ( arg ) ) )
870+ this . setCanvasResource ( operation . resultId , result )
871+ }
872+ break
873+ }
874+ case 'resourceCall' : {
875+ const resource = this . getCanvasResource ( operation . resourceId )
876+ const method = resource ?. [ operation . method ]
877+ if ( typeof method === 'function' ) {
878+ const result = method . apply ( resource , ( operation . args || [ ] ) . map ( arg => this . resolveCanvasArg ( arg ) ) )
879+ this . setCanvasResource ( operation . resultId , result )
880+ }
881+ break
882+ }
883+ case 'createImage' :
884+ this . getCanvasImage ( operation . imageId )
885+ break
886+ case 'imageSetSrc' : {
887+ const image = this . getCanvasImage ( operation . imageId )
888+ image . onload = ( ) => {
889+ this . triggerCallback ( bridgeId , operation . onload , {
890+ width : image . width ,
891+ height : image . height ,
892+ } )
893+ }
894+ image . onerror = ( ) => {
895+ this . triggerCallback ( bridgeId , operation . onerror , {
896+ errMsg : `createImage:fail ${ operation . src } ` ,
897+ } )
898+ }
899+ image . src = operation . src
900+ break
901+ }
902+ default :
903+ console . warn ( '[system]' , '[render]' , `Unsupported canvas node operation: ${ operation . op } ` )
904+ }
905+ }
906+
907+ canvasNodeFlush ( { bridgeId, params } ) {
908+ const node = this . canvasNodes . get ( params . nodeId )
909+ if ( ! node ) {
910+ console . warn ( '[system]' , '[render]' , `canvas node ${ params . nodeId } not found` )
911+ return
912+ }
913+
914+ for ( const operation of params . operations || [ ] ) {
915+ this . executeCanvasOperation ( node , operation , bridgeId )
916+ }
917+ }
918+
919+ canvasNodeRequestAnimationFrame ( { bridgeId, params } ) {
920+ const key = `${ params . nodeId } :${ params . requestId } `
921+ const frameId = requestAnimationFrame ( ( timestamp ) => {
922+ this . canvasRafIds . delete ( key )
923+ this . triggerCallback ( bridgeId , params . callback , timestamp )
924+ } )
925+ this . canvasRafIds . set ( key , frameId )
926+ }
927+
928+ canvasNodeCancelAnimationFrame ( { params } ) {
929+ const key = `${ params . nodeId } :${ params . requestId } `
930+ const frameId = this . canvasRafIds . get ( key )
931+ if ( frameId !== undefined ) {
932+ cancelAnimationFrame ( frameId )
933+ this . canvasRafIds . delete ( key )
934+ }
935+ }
936+
714937 async selectorQuery ( opts ) {
715938 const { bridgeId, params : { tasks, success } } = opts
716939
@@ -886,9 +1109,11 @@ class Runtime {
8861109 data . computedStyle = styles
8871110 }
8881111
889- // TODO: 支持获取 Canvas 和 ScrollViewContext
890- // if (fields.node) {
891- // }
1112+ if ( fields . node ) {
1113+ data . node = isCanvasElement ( targetElement )
1114+ ? this . registerCanvasNode ( targetElement )
1115+ : null
1116+ }
8921117 // TODO: 支持获取 VideoContext、CanvasContext、LivePlayerContext、EditorContext和 MapContext
8931118 // if (fields.context) {
8941119 // }
0 commit comments