index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import baseComponent from '../helpers/baseComponent'
  2. import { toDataURL, getCanvasRef } from '../helpers/hooks/useCanvasAPI'
  3. import { getSystemInfoSync } from '../helpers/hooks/useNativeAPI'
  4. import { useRef } from '../helpers/hooks/useDOM'
  5. import styleToCssString from '../helpers/libs/styleToCssString'
  6. import { getTouchPoints } from '../helpers/shared/gestures'
  7. baseComponent({
  8. properties: {
  9. prefixCls: {
  10. type: String,
  11. value: 'wux-e-sign',
  12. },
  13. type: {
  14. type: String,
  15. value: 'png',
  16. },
  17. width: {
  18. type: [String, Number],
  19. value: 'auto',
  20. },
  21. height: {
  22. type: Number,
  23. value: 200,
  24. },
  25. bgColor: {
  26. type: String,
  27. value: '#ffffff',
  28. },
  29. lineWidth: {
  30. type: Number,
  31. value: 3,
  32. },
  33. lineColor: {
  34. type: String,
  35. value: '#000000',
  36. },
  37. hasFooter: {
  38. type: Boolean,
  39. value: true,
  40. },
  41. cancelText: {
  42. type: String,
  43. value: '重置',
  44. },
  45. confirmText: {
  46. type: String,
  47. value: '确定',
  48. },
  49. },
  50. data: {
  51. isCanvasEmpty: true,
  52. bodyStyle: '',
  53. },
  54. observers: {
  55. ['width, height, bgColor'](width, height, bgColor) {
  56. this.setBodyStyle({
  57. width,
  58. height,
  59. })
  60. this.resize({
  61. ...this.data,
  62. width,
  63. height,
  64. bgColor,
  65. })
  66. },
  67. },
  68. computed: {
  69. classes: ['prefixCls', function(prefixCls) {
  70. const bd = `${prefixCls}__bd`
  71. const ft = `${prefixCls}__ft`
  72. const button = `${prefixCls}__button`
  73. return {
  74. wrap: prefixCls,
  75. bd,
  76. ft,
  77. button,
  78. }
  79. }],
  80. },
  81. methods: {
  82. /**
  83. * 手指触摸动作开始
  84. */
  85. onTouchStart(e) {
  86. if (!this.canvasRef) {
  87. return false
  88. }
  89. const props = this.data
  90. this.canvasRef.then(({ value: ctx }) => {
  91. ctx.beginPath()
  92. ctx.lineWidth = props.lineWidth || 3
  93. ctx.strokeStyle = props.lineColor || '#000000'
  94. this.triggerEvent('start')
  95. })
  96. },
  97. /**
  98. * 手指触摸后移动
  99. */
  100. onTouchMove(e) {
  101. if (!this.canvasRef) {
  102. return false
  103. }
  104. if (this.data.isCanvasEmpty) {
  105. this.setData({ isCanvasEmpty: false })
  106. }
  107. const touch = getTouchPoints(e)
  108. const mouseX = touch.x - (e.currentTarget.offsetLeft || 0)
  109. const mouseY = touch.y - (e.currentTarget.offsetTop || 0)
  110. this.canvasRef.then(({ value: ctx }) => {
  111. ctx.lineCap = 'round'
  112. ctx.lineJoin = 'round'
  113. ctx.lineTo(mouseX, mouseY)
  114. ctx.stroke()
  115. this.triggerEvent('signing', { mouseX, mouseY })
  116. })
  117. },
  118. /**
  119. * 手指触摸动作结束
  120. */
  121. onTouchEnd(e) {
  122. if (this.data.isCanvasEmpty) {
  123. this.setData({ isCanvasEmpty: false })
  124. }
  125. this.triggerEvent('end')
  126. },
  127. /**
  128. * 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中
  129. */
  130. createCanvasContext(props) {
  131. const getWrapRef = () => {
  132. if (props.width === 'auto') {
  133. return useRef(`.${props.prefixCls}__bd`, this)
  134. }
  135. return Promise.resolve({
  136. clientWidth: props.width,
  137. clientHeight: props.height,
  138. })
  139. }
  140. const renderCanvas = () => {
  141. return getWrapRef().then(({ clientWidth: width, clientHeight: height }) => {
  142. return getCanvasRef(props.prefixCls, this).then((canvas) => {
  143. const ctx = canvas.getContext('2d')
  144. const ratio = getSystemInfoSync(['window']).pixelRatio
  145. const canvasWidth = width * ratio
  146. const canvasHeight = height * ratio
  147. const setCanvasBgColor = (ctx) => {
  148. if (ctx && props.bgColor) {
  149. ctx.fillStyle = props.bgColor
  150. ctx.fillRect(0, 0, width, height)
  151. }
  152. }
  153. canvas.width = canvasWidth
  154. canvas.height = canvasHeight
  155. ctx.scale(ratio, ratio)
  156. setCanvasBgColor(ctx)
  157. const clear = () => {
  158. ctx.clearRect(0, 0, width, height)
  159. ctx.closePath()
  160. // reset canvas bgColor
  161. setCanvasBgColor(ctx)
  162. }
  163. const draw = () => {
  164. return toDataURL({ width, height, type: props.type }, canvas)
  165. }
  166. const resize = (createCanvasContext) => {
  167. const data = ctx.getImageData(0, 0, canvasWidth, canvasHeight)
  168. createCanvasContext().then(({ value: newCtx }) => {
  169. newCtx.putImageData(data, 0, 0)
  170. })
  171. }
  172. return { value: ctx, clear, draw, resize }
  173. })
  174. })
  175. }
  176. return Promise.resolve().then(renderCanvas)
  177. },
  178. setBodyStyle(props) {
  179. const bodyStyle = styleToCssString({
  180. width: props.width === 'auto' ? 'auto' : `${props.width}px`,
  181. height: `${props.height}px`,
  182. })
  183. if (this.data.bodyStyle !== bodyStyle) {
  184. this.setData({
  185. bodyStyle,
  186. })
  187. }
  188. },
  189. clear() {
  190. if (this.canvasRef) {
  191. this.canvasRef.then(({ clear }) => {
  192. clear()
  193. this.setData({ isCanvasEmpty: true })
  194. this.triggerEvent('clear')
  195. })
  196. }
  197. },
  198. submit() {
  199. if (this.data.isCanvasEmpty) {
  200. this.triggerEvent('submit', { base64Url: '' })
  201. } else if (this.canvasRef) {
  202. this.canvasRef.then(({ draw }) => {
  203. draw().then((base64Url) => this.triggerEvent('submit', { base64Url }))
  204. })
  205. }
  206. },
  207. resize() {
  208. if (this.canvasRef) {
  209. this.canvasRef.then(({ resize }) => {
  210. resize(() => {
  211. this.canvasRef = this.createCanvasContext(this.data)
  212. return this.canvasRef
  213. })
  214. })
  215. }
  216. },
  217. ['export']() {
  218. return {
  219. resize: this.resize.bind(this),
  220. }
  221. },
  222. },
  223. ready() {
  224. this.setBodyStyle(this.data)
  225. this.canvasRef = this.createCanvasContext(this.data)
  226. },
  227. })