Về hiệu ứng sau cú búng tay của Thanos

Thanos Snap Effect

Hi all,
Hi vọng mọi người đều đã thưởng thức endgame. Dưới đây là đoạn code sưu tầm mô phỏng hiệu ứng búng tay của thanos.( gòole cũng có vụ này)
Mà có 1 vấn đề nhỏ muốn m.n trợ giúp.

Concept

Gồm 3 bước:

  • Chuyển đổi các htmlElement thành image data trong canvas
  • Chia nhỏ các image data vào nhiều canvas khác nhau một cách ngẫu nhiên
  • Animate các canvas sau khi đã chia image data.

Source

Html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script
    src="https://code.jquery.com/jquery-3.4.0.js"
    integrity="sha256-DYZMCC8HTC+QDr5QNaIcfR7VSPtcISykd+6eSmBW5qo="
    crossorigin="anonymous"></script>
    <script
    src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"
    integrity="sha256-eGE6blurk5sHj+rmkfsGYeKyZx3M4bG+ZlFyA7Kns7E="
    crossorigin="anonymous" ></script>
    <script src="main.js"></script>
    <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js" crossorigin="anonymous" ></script>
    <script src="https://chancejs.com/chance.min.js" crossorigin="anonymous" ></script>
    <link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
    <div class="content">
        <div class="love">I Love You 3000</div>
        <button id="start-btn">Snap!</button>
    </div>
</body>
</html>

CSS

* {
    box-sizing: border-box;
  }
  body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background-image:url("https://wallpapercave.com/wp/CToGD7f.jpg");
    background-repeat: no-repeat;
    background-size: cover 
  }
  .content {
    display: flex;
    align-items: center;
    flex-direction: column;  
  }

  #start-btn {
    font-size: 36px;
    margin-top: 30px;
    border: transparent;
    padding: 20px;
    color:orange;
    border-radius: 10px;
    background-size: 50px;
    background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%);
  }
  .dust {
    position: absolute;
  }
  .love{
    font-size: 68px;
    color: white;
    border-radius: 10px;
    padding: 30px;
    background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(121,9,9,1) 0%, rgba(255,94,0,1) 100%);
  }

javascript

var canvasCount = 20;
$(document).ready(function () {   
    $("#start-btn").click(function(){ 
        thanosSnap();
    });
    //  html2canvas($(".content")[0], {
    //     backgroundColor:null,
    //     allowTaint:true,

    //  }).then(function(canvas) {
    //      document.body.appendChild(canvas);
    //  });
});

function thanosSnap(){
    html2canvas($(".content")[0],{        
        backgroundColor:null,
        allowTaint:true
    }).then(canvas => {
        toDust(canvas);
    });
};

function toDust(canvas){
    context = canvas.getContext("2d");
    var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    var imageDataArray = pickDataToImageDataArray(imageData);
 
    for (let i = 0; i < canvasCount; i++) {
        newCanvasFromImageData(imageDataArray[i], canvas.width, canvas.height);
    }
    clearAllExceptCanvas();    

    $(".dust").each( function(index){
        animateBlur($(this),0.8,800);
        setTimeout(() => {
          animateTransform($(this),100,-100,chance.integer({ min: -15, max: 15 }),800+(110*index));
        }, 70*index); 
        //remove the canvas from DOM tree when faded
        $(this).delay(70*index).fadeOut((110*index)+800,"easeInQuint",()=> {$( this ).remove();});
      });
}

function clearAllExceptCanvas() {
    $(".content").children().not(".dust").fadeOut(1500);
}

function createBlankImageData(imageData) {
    var imageDataArray = []; 
    for(let i=0; i<canvasCount; i++)
    {
        let arr = new Uint8ClampedArray(imageData.data);
        for (let j = 0; j < arr.length; j++) {
            arr[j] = 0;
        }
        imageDataArray.push(arr);
    }
    return imageDataArray;
}

function newCanvasFromImageData(imageDataArray ,w , h) {
    var canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    tempCtx = canvas.getContext("2d");
    tempCtx.putImageData(new ImageData(imageDataArray, w , h), 0, 0);
    canvas.classList.add("dust");
    $("body").append(canvas);
    return canvas;
}

function weightedRandomDistrib(peak) {
    var prob = [], seq = [];
    for(let i=0;i<canvasCount;i++) {
      prob.push(Math.pow(canvasCount-Math.abs(peak-i),3));
      seq.push(i);
    }
    return chance.weighted(seq, prob);
}

function pickDataToImageDataArray(imageData) {
    var pixelArr = imageData.data;
    console.log(pixelArr);
    var imageDataArray = createBlankImageData(imageData);
    var c = 0;
    for (let i = 0; i < pixelArr.length; i += 4) {
        //find the highest probability canvas the pixel should be in
        let p = Math.floor((i / pixelArr.length) * canvasCount);
        let a = imageDataArray[weightedRandomDistrib(p)];
        a[i] = pixelArr[i];
        a[i + 1] = pixelArr[i + 1];
        a[i + 2] = pixelArr[i + 2];
        a[i + 3] = pixelArr[i + 3];
    }
    return imageDataArray;
}

function animateBlur(elem,radius,duration) {
    var r = 0;
    $({rad:0}).animate({rad:radius}, {
        duration: duration,
        easing: "easeOutQuad",
        step: function(now) {
          elem.css({
                filter: 'blur(' + now + 'px)'
            });
        }
    });
}
function animateTransform(elem,sx,sy,angle,duration) {
    var td = tx = ty =0;
    $({x: 0, y:0, deg:0}).animate({x: sx, y:sy, deg:angle}, {
        duration: duration,
        easing: "easeInQuad",
        step: function(now, fx) {
            if (fx.prop == "x") 
            tx = now;
            else if (fx.prop == "y") 
            ty = now;
            else if (fx.prop == "deg") 
            td = now;
            elem.css({
                transform: 'rotate(' + td + 'deg)' + 'translate(' + tx + 'px,'+ ty +'px)'
            });
        }
    })
};

function debugImageData(cv1, cv2){
    ctx1 = cv1.getContext("2d");
    imgData = ctx1.getImageData(0,0, cv1.width, cv1.height);
    ctx2 = cv2.getContext("2d");
    ctx2.putImageData(imgData, 0, 0);
}

Vấn đề lớn,

Khi sử dụng element image, mình luôn chỉ thu được ảnh màu trắng thay vì màu sắc của ảnh gốc nguyên nhân do cái Cross-origin policy gây ra. Ai có cách giải quyết giúp mình với. Cảm ơn nhiều. Trong hàm document ready, mình có comment 1 đoạn code nhỏ để debug ảnh thu được sau khi convert các element. Lần đầu làm javascript nên chưa rành vụ này. nhờ anh em trợ giúp.

Code trên do mình sưu tầm trên mạng. Link bài viết gốc Thanos Snap Effect

Mình không muốn dùng codepen hay các dịch vụ tương tự.

4 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?