How to avoid flickering when a functional component is re-render again and again/

82 Views Asked by At

I need help in fixing the code to prevent flashing of Iphone image. I have implemented various things like useMemo and useLayout and debounce and requestAnimationFrame but I am still facing issue

// i have imported useMemo, useRef, useCallback from React
import React, { useState, useMemo, useRef, useCallback } from "react";
import "./Iphone.css";
// I have imported debounce from lodash

import debounce from "lodash.debounce";

// I have created functional component called IPhone

const Iphone = () => {
  // I have used selectedImage and OldImage variable here and initialized them to `null`

  // I have used useMemo on initial loading of page to prevent flashing
  const image = useMemo(() => new Image(), []);
  // I have used useCallback function on initial loading of the page to handleImageChange function 

  // to change a image I called handleImageChange function

  const handleImageChange = useCallback((event) => {
    event.preventDefault();
    const newImageURL = URL.createObjectURL(event.target.files[0]);
    // i have passed newIMageURL to both SetSelectedImage and SetOldImage
    setSelectedImage(newImageURL);
    setOldImage(newImageURL);
  }, []);

  // I have called the function handleText to toggle showing of text
  const handleText = () => {
    setShowText(!showText);
  };

  // I have called useMemo function to prevent flashing as i drag the text to 
  various 
  locations
  const debouncedShowContent = useMemo(
    () => debounce((value) => setContent(value), 300),
    []
  );

  // i have called useCallback function to prevent flashing in handleDragStart 
  function

  const handleDragStart = useCallback(() => {

    // here I am setting dragging to true as i am starting to drag the text
    setDragging(true);
  }, []);
  
  // I have called useCallback function to prevent flashing in handleDrag function

  const handleDrag = useCallback(
    (e) => {
      // i am checking if dragging is true
      if (dragging) {

        // i am calling requestAnimation Frame here

        requestAnimationFrame(() => {

           // I am getting value of canvas via canvasRef.current

          const canvas = canvasRef.current;
          // i am saving value in ctx    
          const ctx = canvas.getContext("2d");
          // i am getting dimensions using canvas.getBoundingClientRect and saving it 
             in rect 
          const rect = canvas.getBoundingClientRect();
          // i am getting value of e.clientX - rect.left and saving it in x variable
          const x = e.clientX - rect.left;
          // i am getting value of e.clientY - rect.top and saving it in y variable
          const y = e.clientY - rect.top;
          // clearing the values of ctx via ctx.clearRect
          ctx.clearRect(0, 0, canvas.width, canvas.height);
           // i am inserting value into image the value oldImage
          image.src = oldImage;
          // on loading of image i am setting value in canvas
          image.onload = () => {
           // drawing image using ctx.drawImage
          ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
          // i am using ctx.fillText to fill the text with value of content and x and 
            y
          ctx.fillText(content, x, y);
             I am setting position of x and y via setPosition
          setPosition({ x, y });
          // converting the value of image to text
          const dataURI = canvas.toDataURL("image/png");
            // saving in setSelectedImage the value of dataURI
          setSelectedImage(dataURI);
          };
        });
      }
    },
    [dragging, oldImage, content]
  );

  // i have called useCallback function to prevent flashing in handleDragEnd 
     function

  const handleDragEnd = useCallback(() => {
    // i am setting the position in x and y via position.x and position.y
    setPosition({ x: position.x, y: position.y });
    // i am setting dragging to false
    setDragging(false);
  }, [position]);

  // i have called useCallback function to prevent flashing in addText function

  const addText = useCallback(() => {
    // here i am setting the image to be true
    setShowImage(true);
     // if image is not selected then do return;
    if (!selectedImage || !content) return;
    textDrawn.current = true;
    requestAnimationFrame(() => {
     // I am getting value of canvas via canvasRef.current
      const canvas = canvasRef.current;
     // i am saving value in ctx    
      const ctx = canvas.getContext("2d");
      // i am inserting value into image the value selectedImage
      image.src = selectedImage;

      // on loading of image i am setting value in canvas
      image.onload = () => {
        // getting width of canvas from image.width
        canvas.width = image.width;
        // getting height of canvas from image.height
        canvas.height = image.height;
        // clearing the values of ctx via ctx.clearRect
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        // drawing image using ctx.drawImage
        ctx.drawImage(image, 0, 0);
        // i am using ctx.fillText to fill the text with value of content
        ctx.fillText(content, textPosition.x, textPosition.y);
        // converting the value of image to text
        const dataURI = canvas.toDataURL("image/png");
        // saving in setSelectedImage the value of dataURI
        setSelectedImage(dataURI);
      };
    });
  }, [selectedImage, content, textPosition]);
  
  // i have used debouncedShowContent function to prevent flashing in showContent 
     function
 
  const showContent = (event) => {
    // calling debouncedShowContent
    debouncedShowContent(event.target.value);
  };
   
  // I am using handleSave function to save the image along with text in new 
  window
  const handleSave = () => { 

    // if image is selected then I am opening new window
    if (selectedImage) {
      // opening a new window
      const newWindow = window.open();
      // writing a new image 
      newWindow.document.write(
        `<img src="${selectedImage}" alt="resultingImage" />`
      );
    }
  };
   // i want to add more details. if you have any questions ask and i will clarify 
      quickly.
};

// I am exporting the Iphone function
export default Iphone;

I want to prevent flashing of image as I drag the text from one location to another. how to do that?

I can add more details. if you have any questions ask and I will clarify quickly.

I have posted comments in each line of the code. Is there anything else I should do to improve the question?

1

There are 1 best solutions below

2
Helder Sepulveda On

I'm going to focus on the handleDrag function ...

Removing all the comments from your code, this is what we get:

    requestAnimationFrame(() => {
      const canvas = canvasRef.current;
      const ctx = canvas.getContext("2d");
      const rect = canvas.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      image.src = oldImage;
      image.onload = () => {
        ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        ctx.fillText(content, x, y);
        setPosition({ x, y });
        const dataURI = canvas.toDataURL("image/png");
        setSelectedImage(dataURI);
      };
    });

First thing that stands out is the constants canvas, ctx and rect you should move those to global constants no need to instantiate those every time we call the drag function, or any other functions related to drawing

Second you have image.onload in this function, I don't think that oldImage is changing value every time, better to do that once outside this function an make the image a global constant


Below are two samples where we can see the difference...

Loading the image once outside the drawing functions, (no flickering)

var x = 0
var imageloaded = false
var image = new Image();
image.src = "http://i.stack.imgur.com/UFBxY.png";
image.onload = () => {
  imageloaded = true
};

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath()
  if (imageloaded) {
    ctx.drawImage(image, 0, 0, 150, 150);
  }
  ctx.arc(x, 40 + Math.sin(x) * 5, 20, 0, 8);
  ctx.fill();
  x = x + 1
}
setInterval(draw, 50);
<canvas id="canvas" width=500></canvas>

Loading the image in the drawing function, (some flickering expected)

var x = 0
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath()
  var image = new Image();
  image.src = "http://i.stack.imgur.com/UFBxY.png";
  image.onload = () => {
    ctx.drawImage(image, 0, 0, 150, 150);
    ctx.arc(x, 40 + Math.sin(x) * 5, 20, 0, 8);
    ctx.fill();
    x = x + 1
  }
}
setInterval(draw, 50);
<canvas id="canvas" width=500></canvas>