Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvas那些事儿 #80

Open
FrankKai opened this issue Jun 22, 2018 · 6 comments
Open

Canvas那些事儿 #80

FrankKai opened this issue Jun 22, 2018 · 6 comments

Comments

@FrankKai
Copy link
Owner

FrankKai commented Jun 22, 2018

记录踩过的坑。

  • 跨域绘制脏canvas问题
  • canvas裁剪图片
  • 如何使两次绘制之间的连续起来?
  • 如何让两次绘制canvas之间有过渡效果?
  • macOS与windows上的canvas.toDataURL()不同
  • 如何解决canvas内存泄漏问题
@FrankKai
Copy link
Owner Author

FrankKai commented Jun 22, 2018

  • 跨域绘制脏canvas问题
    DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.Tainted canvases may not be exported.
canvas.toDataURL('image/jpeg');

绘制异域图片到canvas画布导致矩阵变脏,从而转化base64格式失败,是一个canvas跨域问题。

export class CanvasImage {
  constructor(url, width, height) {
    this._url = url;
    this._element = new Image(width, height);
    this._setImgCrossOrigin(); //必须在获取异域资源前允许img跨域,也就是为img标签设置corssOrigin属性。
    this._setImgSrc();
  }
  _setImgSrc() {
    this._element.src = this._url;
  }
  _setImgCrossOrigin() {
    this._element.setAttribute('crossOrigin', 'Anonymous');
  }
}

@FrankKai
Copy link
Owner Author

FrankKai commented Sep 6, 2018

canvas裁剪图片

思路:根据mousedown起点,mouseup终点计算偏移,重新绘制一次canvas。

<template>
  <div>
    <canvas id="canvas" style="border: 1px solid #ccc" width="260" height="208"></canvas>
    <div>
      <img id="source" src="http://ov6jc8fwp.bkt.clouddn.com/[email protected]">
    </div>
  </div>
</template>

<script>
export default {
  name: 'index',
  data() {
    return {
      dWidth: 260,
      dHeight: '',
      downY: 0,
      upY: 0,
      offset: 0,
    };
  },
  mounted() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = document.getElementById('source');
    this.ctx = ctx;
    this.image = image;
    image.onload = () => {
      const rate = parseFloat((image.width / 260).toFixed(10));
      this.dHeight = image.height / rate;
      ctx.drawImage(image, 0, 0, this.dWidth, this.dHeight);
    };
    canvas.onmousedown = (e) => {
      console.log('帅哥按下了鼠标:', 'x:', e.offsetX, 'y:', e.offsetY);
      this.downY = e.offsetY;
    };
    canvas.onmouseup = (e) => {
      console.log('帅哥松开了鼠标:', 'y:', e.offsetY);
      this.upY = e.offsetY;
      this.offset = this.offset + (this.upY - this.downY);
      this.render(this.offset);
    };
  },
  methods: {
    render(offset) {
      this.ctx.clearRect(0, 0, 260, 208);
      this.ctx.drawImage(this.image, 0, offset, this.dWidth, this.dHeight);
    },
  },
};
</script>

<style scoped>
</style>

image

@FrankKai
Copy link
Owner Author

FrankKai commented Sep 6, 2018

如何使两次绘制之间的连续起来?

思路:通过记录上一次的offsetY值,上一次的offset第一次是mousedown的offsetY值,之后是mousemove的offsetY值,持续重绘canvas,达到连续效果。

<template>
  <div>
    <canvas id="canvas" style="border: 1px solid #ccc" width="260" height="208"></canvas>
    <div>
      <img id="source" src="http://ov6jc8fwp.bkt.clouddn.com/2440018536-58e12656c3533_huge256.jpg">
    </div>
  </div>
</template>

<script>
export default {
  name: 'index',
  data() {
    return {
      dWidth: 260,
      dHeight: '',
      downY: 0,
      offset: 0,
    };
  },
  mounted() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = document.getElementById('source');
    this.ctx = ctx;
    this.image = image;
    image.onload = () => {
      const rate = parseFloat((image.width / 260).toFixed(10));
      this.dHeight = parseFloat(image.height / rate).toFixed(10);
      ctx.drawImage(image, 0, 0, this.dWidth, this.dHeight);
    };


    // begin
    let lastOffsetY;

    const handleMousemove = (e) => {
      const currentOffsetY = e.offsetY;
      this.offset = this.offset + (currentOffsetY - lastOffsetY);
      const topBorder = 0;
      const bottomBorder = -this.dHeight + 208;
      if (this.offset < topBorder && this.offset > bottomBorder) {
        this.render(this.offset);
      } else if (this.offset < bottomBorder) {
        this.offset = bottomBorder;
      } else if (this.offset > topBorder) {
        this.offset = topBorder;
      } 
      lastOffsetY = e.offsetY;
    };

    canvas.addEventListener('mousedown', (e) => {
      console.log('帅哥按下了鼠标:', 'x:', e.offsetX, 'y:', e.offsetY);
      lastOffsetY = e.offsetY;
      this.downY = e.offsetY;
      canvas.addEventListener('mousemove', handleMousemove);
    });
    // end


    canvas.addEventListener('mouseup', (e) => {
      console.log('帅哥松开了鼠标:', 'y:', e.offsetY);
      canvas.removeEventListener('mousemove', handleMousemove);
    });
    canvas.addEventListener('mouseleave', (e) => {
      console.log('帅哥离开了画布:', 'y:', e.offsetY);
      canvas.removeEventListener('mousemove', handleMousemove);
    });
  },
  methods: {
    render(offset) {
      this.ctx.clearRect(0, 0, 260, 208);
      this.ctx.drawImage(this.image, 0, offset, this.dWidth, this.dHeight);
    },
  },
};
</script>

<style scoped>
</style>

@FrankKai
Copy link
Owner Author

FrankKai commented Sep 6, 2018

如何让两次绘制canvas之间有过渡效果?

思路:根据mousemove的offsetY与mousedown的offsetY做比较,给其一个固定的偏移量(我们这里是4),不断重新绘制canvas,达到过渡效果。

<template>
  <div>
    <canvas id="canvas" style="border: 1px solid #ccc" width="260" height="208"></canvas>
    <div>
      <img id="source" src="http://ov6jc8fwp.bkt.clouddn.com/[email protected]">
    </div>
  </div>
</template>

<script>
export default {
  name: 'index',
  data() {
    return {
      dWidth: 260,
      dHeight: '',
      downY: 0,
      moveY: 0,
      offset: 0,
    };
  },
  mounted() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = document.getElementById('source');
    this.ctx = ctx;
    this.image = image;
    image.onload = () => {
      const rate = parseFloat((image.width / 260).toFixed(10));
      this.dHeight = parseFloat(image.height / rate).toFixed(10);
      ctx.drawImage(image, 0, 0, this.dWidth, this.dHeight);
    };



    // begin
    const handleMousemove = (e) => {
      this.moveY = e.offsetY;
      if (this.moveY - this.downY > 0) {
        this.offset = this.offset + 4;
      } else {
        this.offset = this.offset - 4;
      }
      if (this.offset < 0 && this.offset > -this.dHeight + 208) {
        this.render(this.offset);
      }
    };
    // end
    


    canvas.addEventListener('mousedown', (e) => {
      console.log('帅哥按下了鼠标:', 'x:', e.offsetX, 'y:', e.offsetY);
      this.downY = e.offsetY;
      canvas.addEventListener('mousemove', handleMousemove);
    });

    canvas.addEventListener('mouseup', (e) => {
      console.log('帅哥松开了鼠标:', 'y:', e.offsetY);
      canvas.removeEventListener('mousemove', handleMousemove);
    });
    canvas.addEventListener('mouseleave', (e) => {
      console.log('帅哥离开了画布:', 'y:', e.offsetY);
      canvas.removeEventListener('mousemove', handleMousemove);
    });
  },
  methods: {
    render(offset) {
      this.ctx.clearRect(0, 0, 260, 208);
      this.ctx.drawImage(this.image, 0, offset, this.dWidth, this.dHeight);
    },
  },
};
</script>

<style scoped>
</style>

@FrankKai
Copy link
Owner Author

FrankKai commented Sep 21, 2018

macOS与windows上的canvas.toDataURL()不同

场景:合成图片后,通过canvas.toDataURL()将合成canvas转化为base64格式,然后通过七牛SDK上传七牛。
影响:windows失真严重。通过macOS与windows系统上传的图片清晰度不同,假设windows为255KB,macOS为789KB,清晰度macOS约为windows的3倍。
原因:canvas.toDataURL()的生成结果不同,windows 255KB的为347534,macOS 789KB的为1088150。

根本原因:屏幕分辨率不同,无解。

@FrankKai FrankKai changed the title Canvas小册 Canvas那些事儿 Apr 23, 2020
@FrankKai
Copy link
Owner Author

FrankKai commented Apr 10, 2021

如何解决canvas内存泄漏问题

最近遇到一个canvas内存泄漏问题,原因是没有将释放canvas的2D上下文内存导致的。

内存泄漏

import React, { useRef } from 'react'

const canvasComponent = () => {
  const canvasRef = useRef(null)
  const canvasContext = useRef(null)

  useEffect(()=>{
      canvasContext.current = canvasRef.current.getContext('2d')
  }, [])

  const codeStart = () => {
      // ...
  }

  const codeEnd = () => {
      // ...
  }
  return <canvas ref={canvasRef} style={{ display: 'none' }} />
}

export default canvasComponent;

修复内存泄漏

import React, { useRef } from 'react'

const canvasComponent = () => {
  const canvasRef = useRef(null)
  const canvasContext = useRef(null)

  useEffect(()=>{
      canvasContext.current = canvasRef.current.getContext('2d')
  }, [])

  const codeStart = () => {
      canvasContext.current = canvasRef.current.getContext('2d') // 获取新的canvas的2D上下文
      // ...
  }

  const releaseMemory = () =>{
      canvasContext.current = null; // 释放canvas的2D上下文内存
  }

  const codeEnd = () => {
      // ...
      releaseMemory();
  }

  return <canvas ref={canvasRef} style={{ display: 'none' }} />
}

export default canvasComponent;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant