jp-signature.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. <template>
  2. <view class="lime-signature" v-if="show" :style="[canvasStyle, styles]" ref="limeSignature">
  3. <!-- #ifndef APP-VUE || APP-NVUE -->
  4. <canvas v-if="useCanvas2d" class="lime-signature__canvas" :id="canvasId" type="2d"
  5. :disableScroll="disableScroll" @touchstart="touchStart" @touchmove="touchMove"
  6. @touchend="touchEnd"></canvas>
  7. <canvas v-else :disableScroll="disableScroll" class="lime-signature__canvas" :canvas-id="canvasId"
  8. :id="canvasId" :width="canvasWidth" :height="canvasHeight" @touchstart="touchStart" @touchmove="touchMove"
  9. @touchend="touchEnd" @mousedown="touchStart" @mousemove="touchMove" @mouseup="touchEnd"></canvas>
  10. <canvas v-if="showOffscreen" class="offscreen" canvas-id="offscreen" id="offscreen"
  11. :style="'width:' + offscreenSize[0] + 'px;height:' + offscreenSize[1] + 'px'" :width="offscreenSize[0]"
  12. :height="offscreenSize[1]">
  13. </canvas>
  14. <view v-if="showMask" class="mask" @touchstart="touchStart" @touchmove.stop.prevent="touchMove"
  15. @touchend="touchEnd"></view>
  16. <!-- #endif -->
  17. <!-- #ifdef APP-VUE -->
  18. <view :id="canvasId" :disableScroll="disableScroll" :rparam="param" :change:rparam="sign.update"
  19. :rclear="rclear" :change:rclear="sign.clear" :rundo="rundo" :rredo="rredo" :change:rredo="sign.redo"
  20. :change:rundo="sign.undo" :rsave="rsave" :rmask="rmask" :change:rsave="sign.save" :change:rmask="sign.mask"
  21. :rdestroy="rdestroy" :change:rdestroy="sign.destroy" :rempty="rempty" :change:rempty="sign.isEmpty">
  22. </view>
  23. <!-- #endif -->
  24. <!-- #ifdef APP-NVUE -->
  25. <web-view src="/uni_modules/lime-signature/hybrid/html/index.html" class="lime-signature__canvas" ref="webview"
  26. @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage"></web-view>
  27. <!-- #endif -->
  28. </view>
  29. </template>
  30. <script>
  31. // #ifndef APP-NVUE
  32. import {
  33. canIUseCanvas2d,
  34. wrapEvent,
  35. requestAnimationFrame,
  36. sleep,
  37. isTransparent
  38. } from './utils'
  39. import {
  40. Signature
  41. } from './signature.js'
  42. // import {Signature} from '@signature';
  43. import {
  44. uniContext,
  45. createImage,
  46. toDataURL
  47. } from './context'
  48. // #endif
  49. import props from './props';
  50. import {
  51. base64ToPath,
  52. getRect
  53. } from './utils'
  54. import { nextTick } from 'vue';
  55. /**
  56. * LimeSignature 手写板签名
  57. * @description 手写板签名插件:一款能跑在uniapp各端中的签名插件,支持横屏、背景色、笔画颜色、笔画大小等功能,可生成有内容的区域,减小图片尺寸,节省空间。
  58. * @property {Number} penSize 画笔大小
  59. * @property {Number} minLineWidth 线条最小宽
  60. * @property {Number} maxLineWidth 线条最大宽
  61. * @property {String} penColor 画笔颜色
  62. * @property {String} backgroundColor 背景颜色,不填则为透明
  63. * @property {type} 指定 canvas 类型
  64. * @value 2d canvas 2d
  65. * @value '' 非 canvas 2d 旧接口,微信不再维护
  66. * @property {Boolean} openSmooth 模拟笔锋
  67. * @property {Number} beforeDelay 延时初始化,在放在弹窗里可以使用 (毫秒)
  68. * @property {Number} maxHistoryLength 限制历史记录数,即最大可撤销数,传入0则关闭历史记录功能
  69. * @property {Boolean} landscape 横屏,使用后在最后生成图片时会图片旋转90度
  70. * @property {Boolean} disableScroll 当在写字时,禁止屏幕滚动以及下拉刷新,nvue无效
  71. * @property {Boolean} boundingBox 只生成内容区域,即未画部分不生成,有性能的损耗
  72. */
  73. export default {
  74. props,
  75. data() {
  76. return {
  77. canvasWidth: null,
  78. canvasHeight: null,
  79. offscreenWidth: null,
  80. offscreenHeight: null,
  81. useCanvas2d: true,
  82. show: true,
  83. offscreenStyles: '',
  84. showMask: false,
  85. showOffscreen: false,
  86. isPC: false,
  87. // #ifdef APP-PLUS
  88. rclear: 0,
  89. rdestroy: 0,
  90. rundo: 0,
  91. rredo: 0,
  92. rsave: JSON.stringify({
  93. n: 0,
  94. fileType: 'png',
  95. quality: 1,
  96. destWidth: 0,
  97. destHeight: 0,
  98. }),
  99. rmask: JSON.stringify({
  100. n: 0,
  101. destWidth: 0,
  102. destHeight: 0,
  103. }),
  104. rempty: 0,
  105. risEmpty: true,
  106. toDataURL: null,
  107. tempFilePath: [],
  108. // #endif
  109. }
  110. },
  111. computed: {
  112. canvasId() {
  113. // #ifdef VUE2
  114. return `lime-signature${this._uid}`
  115. // #endif
  116. // #ifdef VUE3
  117. return `lime-signature${this._.uid}`
  118. // #endif
  119. },
  120. offscreenId() {
  121. return this.canvasId + 'offscreen'
  122. },
  123. offscreenSize() {
  124. const {
  125. offscreenWidth,
  126. offscreenHeight
  127. } = this
  128. return this.landscape ? [offscreenHeight, offscreenWidth] : [offscreenWidth, offscreenHeight]
  129. },
  130. canvasStyle() {
  131. const {
  132. canvasWidth,
  133. canvasHeight,
  134. backgroundColor
  135. } = this
  136. return {
  137. width: canvasWidth && (canvasWidth + 'px'),
  138. height: canvasHeight && (canvasHeight + 'px'),
  139. background: backgroundColor
  140. }
  141. },
  142. param() {
  143. const {
  144. penColor,
  145. penSize,
  146. backgroundColor,
  147. backgroundImage,
  148. landscape,
  149. boundingBox,
  150. openSmooth,
  151. minLineWidth,
  152. maxLineWidth,
  153. minSpeed,
  154. maxWidthDiffRate,
  155. maxHistoryLength,
  156. disableScroll,
  157. disabled
  158. } = this
  159. return JSON.parse(JSON.stringify({
  160. penColor,
  161. penSize,
  162. backgroundColor,
  163. backgroundImage,
  164. landscape,
  165. boundingBox,
  166. openSmooth,
  167. minLineWidth,
  168. maxLineWidth,
  169. minSpeed,
  170. maxWidthDiffRate,
  171. maxHistoryLength,
  172. disableScroll,
  173. disabled
  174. }))
  175. }
  176. },
  177. // #ifdef APP-NVUE
  178. watch: {
  179. param(v) {
  180. this.$refs.webview.evalJS(`update(${JSON.stringify(v)})`)
  181. }
  182. },
  183. // #endif
  184. // #ifndef APP-PLUS
  185. created() {
  186. const {
  187. platform
  188. } = uni.getSystemInfoSync()
  189. this.isPC = /windows|mac/.test(platform)
  190. this.useCanvas2d = this.type == '2d' && canIUseCanvas2d() && !this.isPC
  191. // #ifndef H5
  192. this.showMask = this.isPC
  193. // #endif
  194. },
  195. // #endif
  196. // #ifndef APP-PLUS
  197. async mounted() {
  198. if (this.beforeDelay) {
  199. await sleep(this.beforeDelay)
  200. }
  201. const config = await this.getContext()
  202. this.signature = new Signature(config)
  203. this.canvasEl = this.signature.canvas.get('el')
  204. this.offscreenWidth = this.canvasWidth = this.signature.canvas.get('width')
  205. this.offscreenHeight = this.canvasHeight = this.signature.canvas.get('height')
  206. this.stopWatch = this.$watch('param', (v) => {
  207. this.signature.pen.setOption(v)
  208. }, {
  209. immediate: true
  210. })
  211. },
  212. // #endif
  213. // #ifndef APP-PLUS
  214. // #ifdef VUE3
  215. beforeUnmount() {
  216. this.stopWatch && this.stopWatch()
  217. this.signature.destroy()
  218. this.signature = null
  219. this.show = false;
  220. // #ifdef APP-VUE || APP-NVUE
  221. this.rdestroy++
  222. // #endif
  223. },
  224. // #endif
  225. // #ifdef VUE2
  226. beforeDestroy() {
  227. this.stopWatch && this.stopWatch()
  228. this.signature.destroy()
  229. this.show = false;
  230. this.signature = null
  231. // #ifdef APP-VUE || APP-NVUE
  232. this.rdestroy++
  233. // #endif
  234. },
  235. // #endif
  236. // #endif
  237. methods: {
  238. // #ifdef MP-QQ
  239. // toJSON() { return this },
  240. // #endif
  241. // #ifdef APP-PLUS
  242. onPageFinish() {
  243. this.$refs.webview.evalJS(`update(${JSON.stringify(this.param)})`)
  244. },
  245. onMessage(e = {}) {
  246. const {
  247. detail: {
  248. data: [res]
  249. }
  250. } = e
  251. if (res.event?.save) {
  252. this.toDataURL = res.event.save
  253. }
  254. if (res.event?.changeSize) {
  255. const {
  256. width,
  257. height
  258. } = res.event.changeSize
  259. }
  260. if (res.event.hasOwnProperty('isEmpty')) {
  261. this.risEmpty = res.event.isEmpty
  262. }
  263. if (res.event?.file) {
  264. this.tempFilePath.push(res.event.file)
  265. if (this.tempFilePath.length > 7) {
  266. this.tempFilePath.shift()
  267. }
  268. return
  269. }
  270. if (res.event?.success) {
  271. if (res.event.success) {
  272. this.tempFilePath.push(res.event.success)
  273. if (this.tempFilePath.length > 8) {
  274. this.tempFilePath.shift()
  275. }
  276. this.toDataURL = this.tempFilePath.join('')
  277. this.tempFilePath = []
  278. } else {
  279. this.$emit('fail', 'canvas no data')
  280. }
  281. return
  282. }
  283. },
  284. // #endif
  285. redo() {
  286. // #ifdef APP-VUE || APP-NVUE
  287. this.rredo += 1
  288. // #endif
  289. // #ifdef APP-NVUE
  290. this.$refs.webview.evalJS(`redo()`)
  291. // #endif
  292. // #ifndef APP-VUE
  293. if (this.signature)
  294. this.signature.redo()
  295. // #endif
  296. },
  297. restore() {
  298. this.redo()
  299. },
  300. undo() {
  301. // #ifdef APP-VUE || APP-NVUE
  302. this.rundo += 1
  303. // #endif
  304. // #ifdef APP-NVUE
  305. this.$refs.webview.evalJS(`undo()`)
  306. // #endif
  307. // #ifndef APP-VUE
  308. if (this.signature)
  309. this.signature.undo()
  310. // #endif
  311. },
  312. clear() {
  313. // #ifdef APP-VUE || APP-NVUE
  314. this.rclear += 1
  315. // #endif
  316. // #ifdef APP-NVUE
  317. this.$refs.webview.evalJS(`clear()`)
  318. // #endif
  319. // #ifndef APP-VUE
  320. if (this.signature)
  321. this.signature.clear()
  322. // #endif
  323. },
  324. isEmpty() {
  325. // #ifdef APP-NVUE
  326. this.$refs.webview.evalJS(`isEmpty()`)
  327. // #endif
  328. // #ifdef APP-VUE || APP-NVUE
  329. this.rempty += 1
  330. // #endif
  331. // #ifndef APP-VUE || APP-NVUE
  332. return this.signature.isEmpty()
  333. // #endif
  334. },
  335. async canvasToMaskPath(param = {}) {
  336. const isEmpty = this.isEmpty()
  337. // #ifdef APP-NVUE
  338. this.$refs.webview.evalJS(`mask(${JSON.stringify(param)})`)
  339. // #endif
  340. // #ifdef APP-VUE || APP-NVUE
  341. const stopURLWatch = this.$watch('toDataURL', (v, n) => {
  342. if (v && v !== n) {
  343. // if(param.pathType == 'url') {
  344. base64ToPath(v).then(res => {
  345. param.success({
  346. tempFilePath: res,
  347. isEmpty: this.risEmpty
  348. })
  349. })
  350. // } else {
  351. // param.success({tempFilePath: v,isEmpty: this.risEmpty })
  352. // }
  353. this.toDataURL = ''
  354. }
  355. stopURLWatch && stopURLWatch()
  356. })
  357. const {
  358. fileType,
  359. quality
  360. } = param
  361. const rmask = JSON.parse(this.rmask)
  362. rmask.n++
  363. rmask.destWidth = param.destWidth ?? 0
  364. rmask.destHeight = param.destHeight ?? 0
  365. // rmask.fileType = fileType
  366. // rmask.quality = quality
  367. this.rmask = JSON.stringify(rmask)
  368. // #endif
  369. // #ifndef APP-VUE || APP-NVUE
  370. this.showOffscreen = true
  371. let width = this.signature.canvas.get('width')
  372. let height = this.signature.canvas.get('height')
  373. let {
  374. pixelRatio
  375. } = uni.getSystemInfoSync()
  376. if (this.useCanvas2d) {
  377. this.offscreenWidth = width * pixelRatio
  378. this.offscreenHeight = height * pixelRatio
  379. } else {
  380. this.offscreenWidth = width
  381. this.offscreenHeight = height
  382. }
  383. await sleep(100)
  384. const context = uni.createCanvasContext('offscreen', this)
  385. const size = Math.max(this.offscreenWidth, this.offscreenHeight)
  386. const success = (success) => param.success && param.success(success)
  387. const fail = (fail) => param.fail && param.fail(fail)
  388. this.signature.pen.getMaskedImageData((imageData) => {
  389. let canvasPutImageData = (options, comp) => {
  390. if (uni.canvasPutImageData) {
  391. uni.canvasPutImageData(options, comp)
  392. } else if (context.putImageData) {
  393. context.putImageData(options)
  394. }
  395. }
  396. canvasPutImageData({
  397. canvasId: 'offscreen',
  398. x: 0,
  399. y: 0,
  400. width: width,
  401. height:height,
  402. data: imageData,
  403. fail(err) {
  404. fail(err)
  405. },
  406. success: (re) => {
  407. toDataURL('offscreen', this, param).then((res) => {
  408. context.restore()
  409. context.clearRect(0, 0, size, size)
  410. this.offscreenWidth = width
  411. this.offscreenHeight = height
  412. this.showOffscreen = false
  413. success({
  414. tempFilePath: res,
  415. isEmpty
  416. })
  417. })
  418. }
  419. }, this)
  420. })
  421. // #endif
  422. },
  423. canvasToTempFilePath(param = {}) {
  424. const isEmpty = this.isEmpty()
  425. // #ifdef APP-NVUE
  426. this.$refs.webview.evalJS(`save(${JSON.stringify(param)})`)
  427. // #endif
  428. // #ifdef APP-VUE || APP-NVUE
  429. const stopURLWatch = this.$watch('toDataURL', (v, n) => {
  430. if (v && v !== n) {
  431. if (this.preferToDataURL) {
  432. param.success({
  433. tempFilePath: v,
  434. isEmpty: this.risEmpty
  435. })
  436. } else {
  437. base64ToPath(v).then(res => {
  438. param.success({
  439. tempFilePath: res,
  440. isEmpty: this.risEmpty
  441. })
  442. })
  443. }
  444. this.toDataURL = ''
  445. }
  446. stopURLWatch && stopURLWatch()
  447. })
  448. const {
  449. fileType,
  450. quality
  451. } = param
  452. const rsave = JSON.parse(this.rsave)
  453. rsave.n++
  454. rsave.fileType = fileType
  455. rsave.quality = quality
  456. rsave.destWidth = param.destWidth ?? 0
  457. rsave.destHeight = param.destHeight ?? 0
  458. this.rsave = JSON.stringify(rsave)
  459. // #endif
  460. // #ifndef APP-VUE || APP-NVUE
  461. const useCanvas2d = this.useCanvas2d
  462. const success = (success) => param.success && param.success(success)
  463. const fail = (err) => param.fail && param.fail(err)
  464. const {
  465. canvas
  466. } = this.signature.canvas.get('el')
  467. const {
  468. backgroundColor,
  469. landscape,
  470. boundingBox
  471. } = this
  472. let width = this.signature.canvas.get('width')
  473. let height = this.signature.canvas.get('height')
  474. let x = 0
  475. let y = 0
  476. const devtools = uni.getSystemInfoSync().platform == 'devtools'
  477. let preferToDataURL = this.preferToDataURL
  478. let scale = 1
  479. // #ifdef MP-TOUTIAO
  480. scale = devtools ? uni.getSystemInfoSync().pixelRatio : scale
  481. // 由于抖音不支持canvasToTempFilePath故优先使用createOffscreenCanvas
  482. preferToDataURL = true
  483. // #endif
  484. const canvasToTempFilePath = async (image) => {
  485. const createCanvasContext = () => {
  486. const useOffscreen = (useCanvas2d && !!uni.createOffscreenCanvas && preferToDataURL)
  487. if (useOffscreen && !devtools) {
  488. const offCanvas = uni.createOffscreenCanvas({
  489. type: '2d'
  490. });
  491. offCanvas.width = this.offscreenSize[0] * scale
  492. offCanvas.height = this.offscreenSize[1] * scale
  493. const context = offCanvas.getContext("2d");
  494. return [context, offCanvas]
  495. } else {
  496. const context = uni.createCanvasContext('offscreen', this)
  497. return [context]
  498. }
  499. }
  500. if (boundingBox && !this.isPC || landscape || backgroundColor && !isTransparent(backgroundColor)) {
  501. this.showOffscreen = true
  502. await sleep(100)
  503. const [context, offCanvas] = createCanvasContext()
  504. context.save()
  505. context.setTransform(1, 0, 0, 1, 0, 0)
  506. if (landscape) {
  507. context.translate(0, width * scale)
  508. context.rotate(-Math.PI / 2)
  509. }
  510. if (backgroundColor && !isTransparent(backgroundColor)) {
  511. context.fillStyle = backgroundColor
  512. context.fillRect(0, 0, width, height)
  513. }
  514. if (offCanvas) {
  515. const img = canvas.createImage();
  516. img.src = image
  517. img.onload = () => {
  518. context.drawImage(img, 0, 0, width * scale, height * scale);
  519. const tempFilePath = offCanvas.toDataURL()
  520. this.showOffscreen = false
  521. success({
  522. tempFilePath,
  523. isEmpty
  524. })
  525. }
  526. } else {
  527. context.drawImage(image, 0, 0, width * scale, height * scale);
  528. context.draw(false, () => {
  529. toDataURL('offscreen', this, param).then((res) => {
  530. const size = Math.max(width, height)
  531. context.restore()
  532. context.clearRect(0, 0, size, size)
  533. this.showOffscreen = false
  534. success({
  535. tempFilePath: res,
  536. isEmpty
  537. })
  538. })
  539. })
  540. }
  541. } else {
  542. success({
  543. tempFilePath: image,
  544. isEmpty
  545. })
  546. }
  547. }
  548. const next = async () => {
  549. if (this.offscreenWidth != width || this.offscreenHeight != height) {
  550. this.offscreenWidth = width
  551. this.offscreenHeight = height
  552. await sleep(100)
  553. }
  554. // #ifndef MP-WEIXIN
  555. const param = {
  556. x,
  557. y,
  558. width,
  559. height,
  560. canvas,
  561. preferToDataURL
  562. }
  563. // #endif
  564. // #ifdef MP-WEIXIN
  565. const param = {
  566. x,
  567. y,
  568. width,
  569. height,
  570. canvas: useCanvas2d ? canvas : null,
  571. preferToDataURL
  572. }
  573. // #endif
  574. toDataURL(this.canvasId, this, param).then(canvasToTempFilePath).catch(fail)
  575. }
  576. // PC端小程序获取不到 ImageData 数据,长度为0
  577. if (boundingBox && !this.isPC) {
  578. this.signature.getContentBoundingBox(async res => {
  579. this.offscreenWidth = width = res.width
  580. this.offscreenHeight = height = res.height
  581. x = res.startX
  582. y = res.startY
  583. next()
  584. })
  585. } else {
  586. next()
  587. }
  588. // #endif
  589. },
  590. // #ifndef APP-PLUS
  591. getContext() {
  592. return getRect(`#${this.canvasId}`, {
  593. context: this,
  594. type: this.useCanvas2d ? 'fields' : 'boundingClientRect'
  595. }).then(res => {
  596. if (res) {
  597. let {
  598. width,
  599. height,
  600. node: canvas,
  601. left,
  602. top,
  603. right
  604. } = res
  605. let {
  606. pixelRatio
  607. } = uni.getSystemInfoSync()
  608. let context;
  609. if (canvas) {
  610. context = canvas.getContext('2d')
  611. canvas.width = width * pixelRatio;
  612. canvas.height = height * pixelRatio;
  613. } else {
  614. pixelRatio = 1
  615. context = uniContext(this.canvasId, this)
  616. canvas = {
  617. getContext: (type) => type == '2d' ? context : null,
  618. createImage,
  619. toDataURL: () => toDataURL(this.canvasId, this),
  620. requestAnimationFrame
  621. }
  622. }
  623. // 支付宝小程序 使用stroke有个默认背景色
  624. context.clearRect(0, 0, width, height)
  625. return {
  626. left,
  627. top,
  628. right,
  629. width,
  630. height,
  631. context,
  632. canvas,
  633. pixelRatio
  634. };
  635. }
  636. })
  637. },
  638. getTouch(e) {
  639. if (this.isPC && this.canvasRect) {
  640. e.touches = e.touches.map(item => {
  641. return {
  642. ...item,
  643. x: item.clientX - this.canvasRect.left,
  644. y: item.clientY - this.canvasRect.top,
  645. }
  646. })
  647. }
  648. return e
  649. },
  650. touchStart(e) {
  651. if (!this.canvasEl) return
  652. this.isStart = true
  653. // 微信小程序PC端不支持事件,使用这方法模拟一下
  654. if (this.isPC) {
  655. getRect(`#${this.canvasId}`, {
  656. context: this
  657. }).then(res => {
  658. this.canvasRect = res
  659. this.canvasEl.dispatchEvent('touchstart', wrapEvent(this.getTouch(e)))
  660. })
  661. return
  662. }
  663. this.canvasEl.dispatchEvent('touchstart', wrapEvent(e))
  664. },
  665. touchMove(e) {
  666. if (!this.canvasEl || !this.isStart && this.canvasEl) return
  667. this.canvasEl.dispatchEvent('touchmove', wrapEvent(this.getTouch(e)))
  668. },
  669. touchEnd(e) {
  670. if (!this.canvasEl) return
  671. this.isStart = false
  672. this.canvasEl.dispatchEvent('touchend', wrapEvent(e))
  673. },
  674. // #endif
  675. }
  676. }
  677. </script>
  678. <!-- #ifdef APP-VUE -->
  679. <script module="sign" lang="renderjs">
  680. import sign from './render'
  681. export default sign
  682. </script>
  683. <!-- #endif -->
  684. <style lang="scss">
  685. .lime-signature,
  686. .lime-signature__canvas {
  687. /* #ifndef APP-NVUE */
  688. position: relative;
  689. width: 100%;
  690. height: 100%;
  691. /* #endif */
  692. /* #ifdef APP-NVUE */
  693. flex: 1;
  694. /* #endif */
  695. }
  696. .mask {
  697. position: absolute;
  698. left: 0;
  699. right: 0;
  700. bottom: 0;
  701. top: 0;
  702. }
  703. .offscreen {
  704. position: fixed;
  705. top: 0;
  706. // left: 0;
  707. pointer-events:none;
  708. // background: rgba(0,255,0,0.5);
  709. left: 9999px;
  710. }
  711. </style>