编程知识 cdmana.com

Use canvas to realize a small screenshot function

design sketch

Don't talk much , Let's see the effect first  Insert picture description here

Realize the idea

  1. Consider this function , It must work hook, Because it's a state thing ,hook You need to return a screenshot , Cancel the function of screenshots and capture pictures
  2. Add two... Under the same parent element node where the picture is located canvas,【canvas A】 It is used to show the dynamic effect of screenshots ( such as , The background of the area not intercepted is grayed out , The intercepted area displays a border );【canvas B】 Used to show the complete picture , It is convenient for intercepting action and generating screenshot data ( remember canvas A and canvas B, I'll use it later )
  3. By means of 【canvas A】 By listening mouseup,mousemove,mousedown Three events calculate the intercepted area , Generate interception action , Generate intercepted pictures, etc
  4. When the screenshot action is completed, the screenshot data is generated immediately and returned

difficulty

1. Calculate the interception area

After the screenshot starts , stay 【canvas A】 Of mousedown Coordinates of the starting point of the event record A, adopt mousemove The event monitors the specific coordinates in real time ,document Class mouseup Event record end coordinates B( The mouse may run out of the screenshot area , So it's in document On the monitor mouseup), With A As a starting point ,B End point ,AB Two points can calculate the interception area

 //  Get the starting point of the screenshot 
【canvas A】.onmousedown = function (e) {
     Record the starting point coordinates A
  }

 //  Get mouse coordinates 
【canvas A】.onmousemove = function ( Coordinate data ) {
    1.  Record mouse coordinates 
    2.  Generate screenshot area dynamic effect 
  }

 //  Get the end point of the screenshot 
 document.addEventListener('mouseup', function (e) {
    1. Record end coordinates 
    2.  Generate screenshots ()
  }
 Copy code 

2 Screenshot animation effect ( The unselected part is grayed out , Cut part, add border, etc )

stay mousedown Put... In the event 【canvas A】 Give ash

   //  Set the gray background for screenshots 
    【canvas A】.fillStyle = 'rgba(0,0,0,0.6)'
    【canvas A】.strokeStyle = 'rgba(0,143,255,1)'
 Copy code 

stay mouseup Draw the intercepted effect on the event

  • First step : mask :globalCompositeOperation = 'source-over' Indicates that the source image is displayed on the target image , Then proceed to fillRect(0, 0, 【canvas A】.width, 【canvas A】.height) That is to draw the gray style we set before on the upper layer of the target image , The first step of ash setting effect is realized
  • The second step : Picture frame :globalCompositeOperation = 'destination-out' Display the target image outside the source image . Only the part of the target image other than the source image will be displayed , The source image is transparent .【canvas A】.fillRect(x, y, w, h) Make the intercepted interior transparent
  • The third step : Stroke : Know everything. , No more details
  // First step : mask 
  【canvas A】.globalCompositeOperation = 'source-over'
  【canvas A】.fillRect(0, 0, 【canvas A】.width, 【canvas A】.height)
  // The second step : Picture frame 
  【canvas A】.globalCompositeOperation = 'destination-out'
  【canvas A】.fillRect(x, y, w, h)
  // The third step : Stroke 
  【canvas A】.globalCompositeOperation = 'source-over'
  【canvas A】.moveTo(x, y)
  【canvas A】.lineTo(x + w, y)
  【canvas A】.lineTo(x + w, y + h)
  【canvas A】.lineTo(x, y + h)
  【canvas A】.lineTo(x, y)
  【canvas A】.stroke()
  【canvas A】.closePath()
 Copy code 

3. Generate & Get a picture of the intercepted area

After the mouse action stops, the screenshot ends , So you need to be in moveup Event generation intercepts image data , Here you can go through canvas Self contained canvas.toDataURL Turn the screenshot into base64, Because by mousedown and mousemove We have obtained the interception area of the user , And at the beginning of the screenshot , Will draw the original picture to 【canvas B】 in , So we can go straight to 【canvas B】 The region is intercepted on the, and then a picture is generated ~

const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const data = 【canvas B】.getImageData(area.x, area.y, area.w, area.h)
canvas.width = area.w
canvas.height = area.h
context.putImageData(data, 0, 0)
return canvas.toDataURL('image/png', 1)
 Copy code 

Complete code

I have encapsulated the screenshot function into a hook, I need to take it myself . It's rough , Feel free to give feedback if you have any questions .

Usage method

This hook Will return three functions init, cut, cancelCut, And screenshot data clipImgData,

  • init: stay init The function passes in the parent element of the screenshot area
  • cut: Start screenshot , You need to pass the original image as a parameter
  • cancelCut: Cancel the screenshot function
  • clipImgData:base64 Screenshot data in format

1. Screenshot function hook

const clip = () => {
  const clipAreaWrap = useRef(null) //  Screenshot area dom
  const clipCanvas = useRef(null) //  For screenshots canvas, And the screenshot starts to generate the screenshot effect ( The background is grayed out )
  const drawCanvas = useRef(null) //  Draw the picture to canvas It's convenient to   Used to generate intercepted pictures base64 data 
  const [clipImgData, setClipImgData] = useState('')

  const init = (wrap) => {
    if (!wrap) return
    clipAreaWrap.current = wrap
    clipCanvas.current = document.createElement('canvas')
    drawCanvas.current = document.createElement('canvas')
    clipCanvas.current.style =
      'width:100%;height:100%;z-index: 2;position: absolute;left: 0;top: 0;'
    drawCanvas.current.style =
      'width:100%;height:100%;z-index: 1;position: absolute;left: 0;top: 0;'

    clipAreaWrap.current.appendChild(clipCanvas.current)
    clipAreaWrap.current.appendChild(drawCanvas.current)
  }
  //  Screenshot 
  const cut = (souceImg: string) => {
    const drawCanvasCtx = drawCanvas.current.getContext('2d')
    const clipCanvasCtx = clipCanvas.current.getContext('2d')

    const wrapWidth = clipAreaWrap.current.clientWidth
    const wrapHeight = clipAreaWrap.current.clientHeight
    clipCanvas.current.width = wrapWidth
    clipCanvas.current.height = wrapHeight
    drawCanvas.current.width = wrapWidth
    drawCanvas.current.height = wrapHeight

    //  Set the gray background for screenshots 
    clipCanvasCtx.fillStyle = 'rgba(0,0,0,0.6)'
    clipCanvasCtx.strokeStyle = 'rgba(0,143,255,1)'

    //  Generate an intercepted region img  Then take it as canvas The first parameter of 
    const clipImg = document.createElement('img')
    clipImg.classList.add('img_anonymous')
    clipImg.crossOrigin = 'anonymous'
    clipImg.src = souceImg

    // Q:  Why do we need append To clipAreaWrap in 
    // A:  Because direct clipImg.src The introduction of is not css Styling ( Mainly width and height ) If you don't append Go straight ahead drawCanvasCtx.drawImage,
    //  It's actually the original size clipImg
    clipAreaWrap.current.appendChild(clipImg)

    //  Draw a screenshot area 
    clipImg.onload = () => {
      // x,y ->  Calculate from drawCanvasCtx Which one of x,y Coordinate points for drawing 
      const x = Math.floor((wrapWidth - clipImg.width) / 2)
      const y = Math.floor((wrapHeight - clipImg.height) / 2)
      // Q:  Why use the width and height of the clone node here 
      // A:  because clipImg The width and height of is in dom Has been css Modified width and height ( Long / wide ) 了 , Instead of the actual length and width of the picture 
      //  Use this width and height in drawCanvasCtx Your drawing will only draw clipImg A small part of ( Because the false width height is smaller than the true width height ), It looks like it's magnified 
      const clipImgCopy = clipImg.cloneNode()
      drawCanvasCtx.drawImage(
        clipImg,
        0,
        0,
        clipImgCopy.width,
        clipImgCopy.height,
        x,
        y,
        clipImg.width,
        clipImg.height
      )
    }

    let start = null

    //  Get the starting point of the screenshot 
    clipCanvas.current.onmousedown = function (e) {
      start = {
        x: e.offsetX,
        y: e.offsetY
      }
    }

    //  Draw screenshot area effect 
    clipCanvas.current.onmousemove = function (e) {
      if (start) {
        fill(
          clipCanvasCtx,
          wrapWidth,
          wrapHeight,
          start.x,
          start.y,
          e.offsetX - start.x,
          e.offsetY - start.y
        )
      }
    }

    //  The screenshot is over , Get screenshot image data 
    document.addEventListener('mouseup', function (e) {
      if (start) {
        var url = getClipPicUrl(
          {
            x: start.x,
            y: start.y,
            w: e.offsetX - start.x,
            h: e.offsetY - start.y
          },
          drawCanvasCtx
        )
        start = null
        // Generate base64 The format of the picture 
        setClipImgData(url)
      }
    })
  }

  const cancelCut = () => {
    clipCanvas.current.width = clipAreaWrap.current.clientWidth
    clipCanvas.current.height = clipAreaWrap.current.clientHeight
    drawCanvas.current.width = clipAreaWrap.current.clientWidth
    drawCanvas.current.height = clipAreaWrap.current.clientHeight
    const drawCanvasCtx = drawCanvas.current.getContext('2d')
    const clipCanvasCtx = clipCanvas.current.getContext('2d')
    drawCanvasCtx.clearRect(
      0,
      0,
      drawCanvas.current.clientWidth,
      drawCanvas.current.clientHeight
    )
    clipCanvasCtx.clearRect(
      0,
      0,
      clipCanvas.current.clientWidth,
      clipCanvas.current.clientHeight
    )
    // Remove mouse events 
    clipCanvas.current.onmousedown = null
    clipCanvas.current.onmousemove = null
  }

  const getClipPicUrl = (area, drawCanvasCtx) => {
    const canvas = document.createElement('canvas')
    const context = canvas.getContext('2d')
    const data = drawCanvasCtx.getImageData(area.x, area.y, area.w, area.h)
    canvas.width = area.w
    canvas.height = area.h
    context.putImageData(data, 0, 0)
    return canvas.toDataURL('image/png', 1)
  }

  //  Draw the effect of screenshot 
  const fill = (context, ctxWidth, ctxHeight, x, y, w, h) => {
    context.clearRect(0, 0, ctxWidth, ctxHeight)
    context.beginPath()
    // mask 
    context.globalCompositeOperation = 'source-over'
    context.fillRect(0, 0, ctxWidth, ctxHeight)
    // Picture frame 
    context.globalCompositeOperation = 'destination-out'
    context.fillRect(x, y, w, h)
    // Stroke 
    context.globalCompositeOperation = 'source-over'
    context.moveTo(x, y)
    context.lineTo(x + w, y)
    context.lineTo(x + w, y + h)
    context.lineTo(x, y + h)
    context.lineTo(x, y)
    // context.stroke()
    context.closePath()
  }
  return { init, cut, cancelCut, clipImgData }
}
 Copy code 

2. html part

import React, { ReactElement, useEffect, useRef, useState } from 'react'
import './index.less'

export default () => {
  const clipAreaWrap = useRef(null) //  Screenshot area dom
  const { init, cut, cancelCut, clipImgData } = clip()

  return (
    <>
      <div className="clip-area-wrap" ref={clipAreaWrap}>
        <img
          className="clip-area-example"
          src={require('../../assets/img/pet/cat-all.png')}
          alt=""
        />
      </div>
      <div className="clip-img-area">
        <img src={clipImgData} alt="" id="img" />
      </div>
      <div className="operation">
        <button
          onClick={() => {
            init(clipAreaWrap.current)
            cut(
              'https://cdn-tos.baohuaxia.com/obj/static-assets/433ed21f7f4a27a5bde94a8119d618c5.png'
            )
          }}
        >
           Screenshot 
        </button>
        <button
          onClick={() => {
            cancelCut()
          }}
        >
           Cancel 
        </button>
      </div>
    </>
  )
}


 Copy code 

3.CSS

.clip-area-wrap {
  height: 450px;
  position: relative;

  // Picture center 
  img {
    width: 100%;
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    max-width: 100%;
    max-height: 100%;
  }
}

// Echo area 
.clip-img-area {
  width: 250px;
  height: 250px;
  position: relative;
  margin: 0 auto;
  // Picture center 
  img {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: 100%;
    max-height: 100%;
  }
}

 Copy code 

Follow up ideas

Later, I want to realize some functions , such as :

  • Automatically put the screenshot into the clipboard
  • Generate and return screenshots in different formats as needed
  • When the picture is too large , Do image compression (canvas.toDataURL Can achieve )
  • ……

The article will be updated continuously , Stay tuned

Reference resources

canvas Realize the screenshot function —— Capture part of the picture

Join us

The financial front-end team is a team that pays equal attention to basic technology and business support , Both solid and excellent technical chassis , It also serves many core businesses of the company : Tiktok payment , Finance , insurance , Securities, etc . At present, a large number of financial front-end teams are concentrated in Beijing , Shenzhen 、 Hangzhou also has an R & D Center , Business is booming everywhere , The team atmosphere is open and lively , We sincerely look forward to like-minded friends joining us !

版权声明
本文为[Byte front end]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/09/20210909135431055e.html

Scroll to Top