HTML5 Canvas에 사각형 그리고 Drag & Drop 구현하기

|

Canvas에 사각형 그리고 Drag & Drop 구현하기

var canvas;
var ctx;

var screenRect;

var rectX, rectY;
var WIDTH = 100, HEIGHT = 50;
var prevMouseX, prevMouseY;
var isMouseDown = false;
var isRectSelected = false;

function init() {
    console.log("init()");
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');

    screenRect = canvas.getBoundingClientRect();

    rectX = canvas.width / 2;
    rectY = canvas.height / 2;

    addKeyEventListener();

    animate();
}

function addKeyEventListener() {

    canvas.onmousedown = function(e) {
        var x = event.clientX - screenRect.left;
        var y = event.clientY - screenRect.top;

        console.log("MouseDown : (" + x + ", " + y + ")");

        prevMouseX = x;
        prevMouseY = y;

        isMouseDown = true;

        if(isRectClicked(x, y)) {
            isRectSelected = true;
        }
        else {
            isRectSelected = false;
        }
    }

    canvas.onmousemove = function(e) {
        if(isMouseDown && isRectSelected) {
            var x = event.clientX - screenRect.left;
            var y = event.clientY - screenRect.top;

            var dx = x - prevMouseX;
            var dy = y - prevMouseY;

            rectX += dx;
            rectY += dy;

            prevMouseX = x;
            prevMouseY = y;
        }     
    }

    canvas.onmouseup = function(e) {
        var x = event.clientX - screenRect.left;
        var y = event.clientY - screenRect.top;

        console.log("MouseUp : (" + x + ", " + y + ")");

        isMouseDown = false;
        isRectSelected = false;
    }

    canvas.onmouseout = function(e) {
        isMouseDown = false;
    }
}

function animate() {
    drawCanvasBackground();
    drawRect();

    requestAnimationFrame(function() {
        animate();
    });
}

function drawCanvasBackground() {
    ctx.fillStyle = 'white';
    ctx.strokeStyle = 'black';

    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.stroke();
}

function drawRect() {
    ctx.strokeStyle = 'black';

    ctx.beginPath();
    ctx.rect(rectX - WIDTH / 2, rectY - HEIGHT / 2, WIDTH, HEIGHT);
    ctx.stroke();
}

function isRectClicked(x, y) {
    if ((x >= (rectX - WIDTH / 2)) && 
        (x <= (rectX + WIDTH / 2)) && 
        (y >= (rectY - HEIGHT / 2)) && 
        (y <= (rectY + HEIGHT / 2))) {
        return true;
    }

    return false;

}

HTML5 ES6 module (import/export) 사용하기

|

ES6 module

ES6(ECMAScript 6, ES2015)부터 module이라는 기능이 생겼습니다.

<script type="module" src="hello.mjs"></script>

기존에 자바스크립트를 불러오는 코드에 type="module" 옵션을 추가해주면 모듈을 불러올 수 있습니다. 쉽게 구분하고 사용할 수 있도록 mjs 확장자를 사용하는 것을 추천하고 있습니다.



모듈의 특징

모듈은 기본적으로 자바스크립트의 특징을 모두 갖고 있습니다. 그리고 추가적으로 다음과 같은 특징이 있습니다.

  • import, export를 사용할 수 있음
  • 모듈 바깥쪽에 선언한 변수들은 전역(Globas scope)가 아닌 Module scope로 선언됨
  • 기본적으로 Strict mode로 동작
  • 같은 모듈을 다른 모듈에서 여러 번 불러도, 모둘 내부 코드는 단 한 번만 실행됨


사용법

모듈 외부에서 사용할 수 있도록 공개된 변수나 함수 앞에 export 키워드를 붙이면 되고, 다른 모듈에서는 import 키워드를 이용해서 불러올 수 있습니다.

export 사용 예제

export const arrs = [10, 20, 30, 40];

export function getName() {
    // ...
}

또는 기본 문법으로 구현된 모듈 뒷 부분에 다음 라인을 추가하면 됩니다.

export { arrs, getName };

아래 예제처럼 alias를 지정할 수도 있습니다.

export { arrs, getName as name };


import 사용 예제

import { arrs, getName } from './sample_module.js';

import arrs from './sample_module.js';

import getName as name from './sample_module.js';

import * as name from './sample_module.js';


예제

module.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Module Test</title>
</head>
<body>
    Hello

    <script type="module">
        import { init } from '/static/js/test.js';

        window.onload = function() {
            console.log("window.onload()");
            init();
        }
    </script>
</body>
</html>


sample_module.js

export const arrs = [10, 20, 30, 40];

const name = "snowdeer";

export function getName() {
    return name;
}


test.js

import { arrs, getName } from './sample_module.js';

console.log(arrs);
printName();

function printName() {
    console.log(getName())
}

export function init() {
    console.log("init()");
    printName();
}

HTML5 jQuery 사용하기

|

jQuery 사용하는 방법

자바스크립트를 사용하다보면, jQuery를 조금만 활용하면 아주 간편하게 구현이 되는 경우가 많아서 어쩔 수 없이 사용하는 경우가 많습니다.

jQuery 사용법은 w3schools과 같은 사이트에서 잘 설명되어 있습니다.

jQuery를 사용하는 방법은 크게 2가지 방법이 있습니다.

  • 직접 파일을 다운받아 사용하는 방법
  • CDN을 이용하는 방법

두 방법 모두 장단 점이 있습니다.


직접 다운받아 사용하는 방법

jquery.com에서 파일을 직접 받은 다음 다음 코드로 불러올 수 있습니다.

<head>
<script src="jquery-3.4.1.min.js"></script>
</head>


CDN을 이용하는 방법

CDN(Content Delivery Network)을 이용하면 파일을 다운로드 할 필요 없이 다음 호출 코드만으로 jQuery를 사용할 수 있어서 편리합니다.

<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>

또는

<head>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script>
</head>

HTML5 Canvas의 마우스 클릭 위치 얻기

|

Canvas의 마우스 클릭 위치 얻기

var canvas;
var ctx;

function init() {
    console.log("init()");
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');

    drawCanvasBorder();

    canvas.addEventListener("click", function(event) {
        var rect = canvas.getBoundingClientRect();
        var x = event.clientX - rect.left;
        var y = event.clientY - rect.top;

        console.log("(" + x + ", " + y + ") is clicked.");
    });
}

function drawCanvasBorder() {
    ctx.strokeStyle = 'black';

    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.stroke();
}

HTML5 Tetris 게임 만들기 - (8) 다 채워진 라인 삭제 및 전체 코드

|

다 채워진 라인 삭제 기능 구현

라인 삭제 기능은 2차 배열의 모든 칸이 1로 채워있는지 체크만 하면 되서 간편합니다.

function handleLineClear() {
    var r = ROWS - 1;
    while(r >= 0) {
        var count = 0;
        for(var c=0; c < COLS; c++) {
            count += grid[r][c];
        }

        if(count == COLS) {
            removeLine(r);
            continue;
        }

        r--;
    }
}

function removeLine(row) {
    for(var r = row; r >= 0; r--) {
        if(r == 0) {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = 0;
            }
        }
        else {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = grid[r - 1][c];
            }
        }
    }
}


전체 코드

var canvas;
var ctx;

var x, y;

const BLOCK_DROP_DELAY = 100;
var lastBlockDownTime = 0;

const FRAME = 60;
var drawingTimeDelay = 1000/FRAME;

const ROWS = 24;
const COLS = 12;
var grid;

var cellWidth;
var cellHeight;

const BLOCK_SHAPE = {
    shape1 : [[0, 0], [1, 0], [0, 1], [1, 1]],
    shape2 : [[0, -1], [0, 0], [1, 0], [0, 1]],
    shape3 : [[0, -1], [0, 0], [0, 1], [0, 2]],
    shape4 : [[0, -1], [0, 0], [0, 1], [1, 1]],
    shape5 : [[0, -1], [0, 0], [1, 0], [1, 1]],
};
var curShape = BLOCK_SHAPE.shape1;

//console.log("curShape size: " + curShape.length);

function init() {
    console.log("init()");
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');

    initGrid(COLS, ROWS);
    initBlock();    

    addKeyEventListener();
}

function initBlock() {
    x = COLS / 2;
    y = 0;

    const keys = Object.keys(BLOCK_SHAPE);
    const randomIdx = Math.floor(Math.random() * keys.length);
    curShape = BLOCK_SHAPE[keys[randomIdx]].slice();
}

function initGrid(cols, rows) {
    console.log("initGrid(" + cols + ", " + rows + ")");

    cellWidth = canvas.width / COLS;
    cellHeight = canvas.height / ROWS;

    grid = new Array(rows);
    for(var i=0; i < rows; i++) {
        grid[i] = new Array(cols);
    }

    for(var r=0; r < rows; r++) {
        for(var c=0; c < cols; c++) {
            grid[r][c] = 0;
        }
    }
}

function start() {
   animate(-1);
}

function draw() {
    drawBackground();
    drawGridLine();
    drawBlocks();
    drawFallingBlock();
}

function drawBackground() {
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function drawGridLine() {
    ctx.strokeStyle = 'gray';

    for(var i=1; i < COLS; i++) {
        ctx.beginPath();
        ctx.moveTo(i * cellWidth, 0);
        ctx.lineTo(i * cellWidth, canvas.height);
        ctx.stroke();
    }

    for(var i=1; i < ROWS; i++) {
        ctx.beginPath();
        ctx.moveTo(0, i * cellHeight);
        ctx.lineTo(canvas.width, i * cellHeight);
        ctx.stroke();
    }
}

function drawBlocks() {
    for(var r=0; r < ROWS; r++) {
        for(var c=0; c < COLS; c++) {
            if(grid[r][c] == 1) {
                fillGrid(c, r);
            }
        }
    }
}

function fillGrid(x, y) {
    ctx.fillStyle = 'green';

    var canvasX = x * cellWidth;
    var canvasY = y * cellHeight;

    ctx.fillRect(canvasX, canvasY, cellWidth, cellHeight);
}

function drawFallingBlock() {
    ctx.fillStyle = 'orange';

    for(var i=0; i < curShape.length; i++) {
        var canvasX = (x + curShape[i][0]) * cellWidth;
        var canvasY = (y + curShape[i][1]) * cellHeight;

        ctx.fillRect(canvasX, canvasY, cellWidth, cellHeight);
    }
}

function addKeyEventListener() {
    addEventListener('keydown', function(event) {
        switch(event.key) {
            case 'ArrowLeft':
                console.log("Left");
                if(canBlockMoveTo(x - 1, y, curShape)) {
                    x -= 1;
                }
                break;

            case 'ArrowRight':
                console.log("Right");
                if(canBlockMoveTo(x + 1, y, curShape)) {
                    x += 1;
                }
            break;

            case 'ArrowUp':
                console.log("Up");
                handleRotate();
                break;

            case 'ArrowDown':
                console.log("Down");
                dropBlock();
                break;
        }
    });
}

function animate(lastTime) {
    var curTime = (new Date()).getTime();
    var diff = curTime - lastTime;
    
    if(diff > drawingTimeDelay) {
        draw();

        lastTime = curTime;
    }

    handleBlockDown();
    handleLineClear();

    requestAnimationFrame(function() {
        animate(lastTime);
    });
}

function handleBlockDown() {
    var curTime = (new Date()).getTime();
    var diff = curTime - lastBlockDownTime;

    if(diff > BLOCK_DROP_DELAY) {
        if(canBlockMoveTo(x, y + 1, curShape)) {
            y += 1;    
        }
        else {
            console.log("block down stopped(x: " + x, "y: " + y + ")");

            for(var i=0; i < curShape.length; i++) {
                var shapeX = x + curShape[i][0];
                var shapeY = y + curShape[i][1];
                grid[shapeY][shapeX] = 1;
            }
            
            initBlock();
        }
        lastBlockDownTime = curTime;
    }
}

function canBlockMoveTo(toX, toY, shape) {
    for(var i=0; i < shape.length; i++) {
        var shapeX = toX + shape[i][0];
        var shapeY = toY + shape[i][1];

        if(shapeX < 0) return false;
        if(shapeX >= COLS) return false;
        if(shapeY < 0) return false;
        if(shapeY >= ROWS) return false;
        
        if(grid[shapeY][shapeX] == 1) return false;
    }

    return true;
}

function dropBlock() {
    for(var toY=ROWS-1; toY >= 0; toY--) {
        if(canBlockMoveTo(x, toY, curShape)) {
            y = toY;    
            return;
        }
    }
}

function handleRotate() {
    var nextShape = new Array(curShape.length);

    for(var i=0; i < curShape.length; i++) {
        nextShape[i] = new Array(2);        
        nextShape[i][0] = curShape[i][1];
        nextShape[i][1] = -curShape[i][0];
    }

    if(canBlockMoveTo(x, y, nextShape)) {
        curShape = nextShape;
    }
}

function handleLineClear() {
    var r = ROWS - 1;
    while(r >= 0) {
        var count = 0;
        for(var c=0; c < COLS; c++) {
            count += grid[r][c];
        }

        if(count == COLS) {
            removeLine(r);
            continue;
        }

        r--;
    }
}

function removeLine(row) {
    for(var r = row; r >= 0; r--) {
        if(r == 0) {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = 0;
            }
        }
        else {
            for(var c=0; c < COLS; c++) {
                grid[r][c] = grid[r - 1][c];
            }
        }
    }
}