canvas.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. const cacheChart = {}
  2. const fontSizeReg = /([\d\.]+)px/;
  3. class EventEmit {
  4. constructor() {
  5. this.__events = {};
  6. }
  7. on(type, listener) {
  8. if (!type || !listener) {
  9. return;
  10. }
  11. const events = this.__events[type] || [];
  12. events.push(listener);
  13. this.__events[type] = events;
  14. }
  15. emit(type, e) {
  16. if (type.constructor === Object) {
  17. e = type;
  18. type = e && e.type;
  19. }
  20. if (!type) {
  21. return;
  22. }
  23. const events = this.__events[type];
  24. if (!events || !events.length) {
  25. return;
  26. }
  27. events.forEach((listener) => {
  28. listener.call(this, e);
  29. });
  30. }
  31. off(type, listener) {
  32. const __events = this.__events;
  33. const events = __events[type];
  34. if (!events || !events.length) {
  35. return;
  36. }
  37. if (!listener) {
  38. delete __events[type];
  39. return;
  40. }
  41. for (let i = 0, len = events.length; i < len; i++) {
  42. if (events[i] === listener) {
  43. events.splice(i, 1);
  44. i--;
  45. }
  46. }
  47. }
  48. }
  49. class Image {
  50. constructor() {
  51. this.currentSrc = null
  52. this.naturalHeight = 0
  53. this.naturalWidth = 0
  54. this.width = 0
  55. this.height = 0
  56. this.tagName = 'IMG'
  57. }
  58. set src(src) {
  59. this.currentSrc = src
  60. uni.getImageInfo({
  61. src,
  62. success: (res) => {
  63. this.naturalWidth = this.width = res.width
  64. this.naturalHeight = this.height = res.height
  65. this.onload()
  66. },
  67. fail: () => {
  68. this.onerror()
  69. }
  70. })
  71. }
  72. get src() {
  73. return this.currentSrc
  74. }
  75. }
  76. class OffscreenCanvas {
  77. constructor(ctx, com, canvasId) {
  78. this.tagName = 'canvas'
  79. this.com = com
  80. this.canvasId = canvasId
  81. this.ctx = ctx
  82. }
  83. set width(w) {
  84. this.com.offscreenWidth = w
  85. }
  86. set height(h) {
  87. this.com.offscreenHeight = h
  88. }
  89. get width() {
  90. return this.com.offscreenWidth || 0
  91. }
  92. get height() {
  93. return this.com.offscreenHeight || 0
  94. }
  95. getContext(type) {
  96. return this.ctx
  97. }
  98. getImageData() {
  99. return new Promise((resolve, reject) => {
  100. this.com.$nextTick(() => {
  101. uni.canvasGetImageData({
  102. x:0,
  103. y:0,
  104. width: this.com.offscreenWidth,
  105. height: this.com.offscreenHeight,
  106. canvasId: this.canvasId,
  107. success: (res) => {
  108. resolve(res)
  109. },
  110. fail: (err) => {
  111. reject(err)
  112. },
  113. }, this.com)
  114. })
  115. })
  116. }
  117. }
  118. export class Canvas {
  119. constructor(ctx, com, isNew, canvasNode={}) {
  120. cacheChart[com.canvasId] = {ctx}
  121. this.canvasId = com.canvasId;
  122. this.chart = null;
  123. this.isNew = isNew
  124. this.tagName = 'canvas'
  125. this.canvasNode = canvasNode;
  126. this.com = com;
  127. if (!isNew) {
  128. this._initStyle(ctx)
  129. }
  130. this._initEvent();
  131. this._ee = new EventEmit()
  132. }
  133. getContext(type) {
  134. if (type === '2d') {
  135. return this.ctx;
  136. }
  137. }
  138. setAttribute(key, value) {
  139. if(key === 'aria-label') {
  140. this.com['ariaLabel'] = value
  141. }
  142. }
  143. setChart(chart) {
  144. this.chart = chart;
  145. }
  146. createOffscreenCanvas(param){
  147. if(!this.children) {
  148. this.com.isOffscreenCanvas = true
  149. this.com.offscreenWidth = param.width||300
  150. this.com.offscreenHeight = param.height||300
  151. const com = this.com
  152. const canvasId = this.com.offscreenCanvasId
  153. const context = uni.createCanvasContext(canvasId, this.com)
  154. this._initStyle(context)
  155. this.children = new OffscreenCanvas(context, com, canvasId)
  156. }
  157. return this.children
  158. }
  159. appendChild(child) {
  160. console.log('child', child)
  161. }
  162. dispatchEvent(type, e) {
  163. if(typeof type == 'object') {
  164. this._ee.emit(type.type, type);
  165. } else {
  166. this._ee.emit(type, e);
  167. }
  168. return true
  169. }
  170. attachEvent() {
  171. }
  172. detachEvent() {
  173. }
  174. addEventListener(type, listener) {
  175. this._ee.on(type, listener)
  176. }
  177. removeEventListener(type, listener) {
  178. this._ee.off(type, listener)
  179. }
  180. _initCanvas(zrender, ctx) {
  181. // zrender.util.getContext = function() {
  182. // return ctx;
  183. // };
  184. // zrender.util.$override('measureText', function(text, font) {
  185. // ctx.font = font || '12px sans-serif';
  186. // return ctx.measureText(text, font);
  187. // });
  188. }
  189. _initStyle(ctx, child) {
  190. const styles = [
  191. 'fillStyle',
  192. 'strokeStyle',
  193. 'fontSize',
  194. 'globalAlpha',
  195. 'opacity',
  196. 'textAlign',
  197. 'textBaseline',
  198. 'shadow',
  199. 'lineWidth',
  200. 'lineCap',
  201. 'lineJoin',
  202. 'lineDash',
  203. 'miterLimit',
  204. // #ifdef H5
  205. 'font',
  206. // #endif
  207. ];
  208. const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
  209. styles.forEach(style => {
  210. Object.defineProperty(ctx, style, {
  211. set: value => {
  212. // #ifdef H5
  213. if (style === 'font' && fontSizeReg.test(value)) {
  214. const match = fontSizeReg.exec(value);
  215. ctx.setFontSize(match[1]);
  216. return;
  217. }
  218. // #endif
  219. if (style === 'opacity') {
  220. ctx.setGlobalAlpha(value)
  221. return;
  222. }
  223. if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
  224. // #ifdef H5 || APP-PLUS || MP-BAIDU
  225. if(typeof value == 'object') {
  226. if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
  227. ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
  228. }
  229. return
  230. }
  231. // #endif
  232. // #ifdef MP-TOUTIAO
  233. if(colorReg.test(value)) {
  234. value = value.replace(colorReg, '#$1$1$2$2$3$3')
  235. }
  236. // #endif
  237. ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
  238. }
  239. }
  240. });
  241. });
  242. if(!this.isNew && !child) {
  243. ctx.uniDrawImage = ctx.drawImage
  244. ctx.drawImage = (...a) => {
  245. a[0] = a[0].src
  246. ctx.uniDrawImage(...a)
  247. }
  248. }
  249. if(!ctx.createRadialGradient) {
  250. ctx.createRadialGradient = function() {
  251. return ctx.createCircularGradient(...[...arguments].slice(-3))
  252. };
  253. }
  254. // 字节不支持
  255. if (!ctx.strokeText) {
  256. ctx.strokeText = (...a) => {
  257. ctx.fillText(...a)
  258. }
  259. }
  260. // 钉钉不支持 , 鸿蒙是异步
  261. if (!ctx.measureText || uni.getSystemInfoSync().osName == 'harmonyos') {
  262. ctx._measureText = ctx.measureText
  263. const strLen = (str) => {
  264. let len = 0;
  265. for (let i = 0; i < str.length; i++) {
  266. if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
  267. len++;
  268. } else {
  269. len += 2;
  270. }
  271. }
  272. return len;
  273. }
  274. ctx.measureText = (text, font) => {
  275. let fontSize = ctx?.state?.fontSize || 12;
  276. if (font) {
  277. fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
  278. }
  279. fontSize /= 2;
  280. let isBold = fontSize >= 16;
  281. const widthFactor = isBold ? 1.3 : 1;
  282. // ctx._measureText(text, (res) => {})
  283. return {
  284. width: strLen(text) * fontSize * widthFactor
  285. };
  286. }
  287. }
  288. }
  289. _initEvent(e) {
  290. this.event = {};
  291. const eventNames = [{
  292. wxName: 'touchStart',
  293. ecName: 'mousedown'
  294. }, {
  295. wxName: 'touchMove',
  296. ecName: 'mousemove'
  297. }, {
  298. wxName: 'touchEnd',
  299. ecName: 'mouseup'
  300. }, {
  301. wxName: 'touchEnd',
  302. ecName: 'click'
  303. }];
  304. eventNames.forEach(name => {
  305. this.event[name.wxName] = e => {
  306. const touch = e.touches[0];
  307. this.chart.getZr().handler.dispatch(name.ecName, {
  308. zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
  309. zrY: name.wxName === 'tap' ? touch.clientY : touch.y
  310. });
  311. };
  312. });
  313. }
  314. set width(w) {
  315. this.canvasNode.width = w
  316. }
  317. set height(h) {
  318. this.canvasNode.height = h
  319. }
  320. get width() {
  321. return this.canvasNode.width || 0
  322. }
  323. get height() {
  324. return this.canvasNode.height || 0
  325. }
  326. get ctx() {
  327. return cacheChart[this.canvasId]['ctx'] || null
  328. }
  329. set chart(chart) {
  330. cacheChart[this.canvasId]['chart'] = chart
  331. }
  332. get chart() {
  333. return cacheChart[this.canvasId]['chart'] || null
  334. }
  335. }
  336. export function dispatch(name, {x,y, wheelDelta}) {
  337. this.dispatch(name, {
  338. zrX: x,
  339. zrY: y,
  340. zrDelta: wheelDelta,
  341. preventDefault: () => {},
  342. stopPropagation: () =>{}
  343. });
  344. }
  345. export function setCanvasCreator(echarts, {canvas, node}) {
  346. // echarts.setCanvasCreator(() => canvas);
  347. if(echarts && !echarts.registerPreprocessor) {
  348. return console.warn('echarts 版本不对或未传入echarts,vue3请使用esm格式')
  349. }
  350. echarts.registerPreprocessor(option => {
  351. if (option && option.series) {
  352. if (option.series.length > 0) {
  353. option.series.forEach(series => {
  354. series.progressive = 0;
  355. });
  356. } else if (typeof option.series === 'object') {
  357. option.series.progressive = 0;
  358. }
  359. }
  360. });
  361. function loadImage(src, onload, onerror) {
  362. let img = null
  363. if(node && node.createImage) {
  364. img = node.createImage()
  365. img.onload = onload.bind(img);
  366. img.onerror = onerror.bind(img);
  367. img.src = src;
  368. return img
  369. } else {
  370. img = new Image()
  371. img.onload = onload.bind(img)
  372. img.onerror = onerror.bind(img);
  373. img.src = src
  374. return img
  375. }
  376. }
  377. if(echarts.setPlatformAPI) {
  378. echarts.setPlatformAPI({
  379. loadImage: canvas.setChart ? loadImage : null,
  380. createCanvas(){
  381. const key = 'createOffscreenCanvas'
  382. return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
  383. }
  384. })
  385. }
  386. }