canvas.js 8.5 KB

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