FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

Canvas那些事儿 #80

Open FrankKai opened 6 years ago

FrankKai commented 6 years ago

记录踩过的坑。

FrankKai commented 6 years ago
FrankKai commented 6 years ago

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/QQ20180906-171620@2x.png">
    </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 commented 6 years ago

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

思路:通过记录上一次的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 commented 6 years ago

如何让两次绘制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/QQ20180906-171620@2x.png">
    </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 commented 5 years ago

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 commented 3 years ago

如何解决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;