728x90

그림판

선,원,사각형 및 레이어 있는 그림판 

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>그림판</title>

<style>

*{
    box-sizing:border-box;
}

body{
    margin:0;
    font-family:Arial,sans-serif;
    background:#dfe4ea;
    overflow:hidden;
}

.topbar{
    display:flex;
    flex-wrap:wrap;
    gap:10px;
    padding:10px;
    background:#222;
    color:white;
    align-items:center;
}

button{
    border:none;
    padding:8px 14px;
    border-radius:6px;
    cursor:pointer;
    background:#444;
    color:white;
}

button.active{
    background:#1e90ff;
}

.toggleBtn{
    background:#111;
    padding:4px 10px;
    font-size:12px;
}

.layout{
    display:flex;
    height:calc(100vh - 70px);
}

.sidebar{
    width:240px;
    background:#2f3542;
    color:white;
    padding:10px;
    overflow:auto;
}

.title{
    font-size:18px;
    margin-bottom:10px;
    font-weight:bold;
}

.layerItem{
    background:#57606f;
    padding:10px;
    border-radius:6px;
    margin-bottom:8px;
    cursor:pointer;
}

.layerItem.active{
    background:#1e90ff;
}

.canvasWrap{
    flex:1;
    overflow:auto;
    background:#b2bec3;
}

#canvasContainer{
    position:relative;
    width:1400px;
    height:900px;
    background:white;
    margin:20px auto;
    border:2px solid #333;
}

canvas{
    position:absolute;
    left:0;
    top:0;
}

input[type="color"]{
    width:50px;
    height:40px;
    border:none;
    cursor:pointer;
}

</style>
</head>
<body>

<div class="topbar">

    <button data-tool="pen" class="active">펜</button>
    <button data-tool="line">선</button>
    <button data-tool="rect">사각형</button>
    <button data-tool="circle">원</button>
    <button data-tool="eraser">지우개</button>

    색상
    <input type="color" id="colorPicker" value="#000000">

    굵기
    <input type="range" id="sizePicker" min="1" max="50" value="3">

    <button id="addLayerBtn">레이어 추가</button>

    <button id="saveBtn">PNG 저장</button>

    <input type="file" id="imageLoader" accept="image/*">

</div>

<div class="layout">

    <div class="sidebar">

        <div class="title">레이어</div>

        <div id="layerList"></div>

    </div>

    <div class="canvasWrap">

        <div id="canvasContainer"></div>

    </div>

</div>

<script>

const WIDTH = 1400;
const HEIGHT = 900;

const canvasContainer = document.getElementById("canvasContainer");
const layerList = document.getElementById("layerList");

const colorPicker = document.getElementById("colorPicker");
const sizePicker = document.getElementById("sizePicker");

const addLayerBtn = document.getElementById("addLayerBtn");
const saveBtn = document.getElementById("saveBtn");

const imageLoader = document.getElementById("imageLoader");

const toolButtons = document.querySelectorAll("[data-tool]");

let currentTool = "pen";

let layers = [];
let activeLayerIndex = 0;

let drawing = false;

let startX = 0;
let startY = 0;

let savedImage = null;

toolButtons.forEach(btn=>{

    btn.addEventListener("click",()=>{

        toolButtons.forEach(b=>b.classList.remove("active"));

        btn.classList.add("active");

        currentTool = btn.dataset.tool;

    });

});

function createLayer(name){

    const canvas = document.createElement("canvas");

    canvas.width = WIDTH;
    canvas.height = HEIGHT;

    canvas.style.zIndex = layers.length;

    canvasContainer.appendChild(canvas);

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

    ctx.lineCap = "round";
    ctx.lineJoin = "round";

    const layer = {
        name,
        canvas,
        ctx,
        visible:true
    };

    layers.push(layer);

    attachCanvasEvents(canvas);

    refreshLayerUI();

}

function refreshLayerUI(){

    layerList.innerHTML = "";

    layers.forEach((layer,index)=>{

        const div = document.createElement("div");

        div.className = "layerItem";

        if(index === activeLayerIndex){

            div.classList.add("active");

        }

        div.innerHTML = `
            <div style="
                display:flex;
                justify-content:space-between;
                align-items:center;
                gap:10px;
            ">
                <span>${layer.name}</span>

                <button class="toggleBtn">
                    ${layer.visible ? "ON" : "OFF"}
                </button>
            </div>
        `;

        div.addEventListener("click",()=>{

            activeLayerIndex = index;

            refreshLayerUI();

        });

        const toggleBtn = div.querySelector(".toggleBtn");

        toggleBtn.addEventListener("click",(e)=>{

            e.stopPropagation();

            layer.visible = !layer.visible;

            layer.canvas.style.display =
                layer.visible ? "block" : "none";

            refreshLayerUI();

        });

        layerList.appendChild(div);

    });

}

function getActiveLayer(){

    return layers[activeLayerIndex];

}

function attachCanvasEvents(canvas){

    canvas.addEventListener("mousedown",startDraw);

    canvas.addEventListener("mousemove",draw);

    canvas.addEventListener("mouseup",stopDraw);

    canvas.addEventListener("mouseleave",stopDraw);

}

function startDraw(e){

    drawing = true;

    startX = e.offsetX;
    startY = e.offsetY;

    const ctx = getActiveLayer().ctx;

    savedImage = ctx.getImageData(0,0,WIDTH,HEIGHT);

    if(currentTool === "pen" || currentTool === "eraser"){

        ctx.beginPath();

        ctx.moveTo(startX,startY);

    }

}

function draw(e){

    if(!drawing) return;

    const x = e.offsetX;
    const y = e.offsetY;

    const ctx = getActiveLayer().ctx;

    ctx.lineWidth = sizePicker.value;

    ctx.strokeStyle =
        currentTool === "eraser"
        ? "#ffffff"
        : colorPicker.value;

    // 펜 / 지우개
    if(currentTool === "pen" || currentTool === "eraser"){

        ctx.lineTo(x,y);

        ctx.stroke();

    }

    // 선
    else if(currentTool === "line"){

        ctx.putImageData(savedImage,0,0);

        ctx.beginPath();

        ctx.moveTo(startX,startY);

        ctx.lineTo(x,y);

        ctx.stroke();

    }

    // 사각형
    else if(currentTool === "rect"){

        ctx.putImageData(savedImage,0,0);

        ctx.beginPath();

        ctx.rect(
            startX,
            startY,
            x-startX,
            y-startY
        );

        ctx.stroke();

    }

    // 원
    else if(currentTool === "circle"){

        ctx.putImageData(savedImage,0,0);

        const radius = Math.sqrt(
            Math.pow(x-startX,2)+
            Math.pow(y-startY,2)
        );

        ctx.beginPath();

        ctx.arc(
            startX,
            startY,
            radius,
            0,
            Math.PI*2
        );

        ctx.stroke();

    }

}

function stopDraw(){

    drawing = false;

}

addLayerBtn.addEventListener("click",()=>{

    createLayer(
        "레이어 " + (layers.length + 1)
    );

});

saveBtn.addEventListener("click",()=>{

    const exportCanvas =
        document.createElement("canvas");

    exportCanvas.width = WIDTH;
    exportCanvas.height = HEIGHT;

    const exportCtx =
        exportCanvas.getContext("2d");

    layers.forEach(layer=>{

        if(layer.visible){

            exportCtx.drawImage(
                layer.canvas,
                0,
                0
            );

        }

    });

    const link = document.createElement("a");

    link.download = "drawing.png";

    link.href =
        exportCanvas.toDataURL("image/png");

    link.click();

});

imageLoader.addEventListener("change",(e)=>{

    const file = e.target.files[0];

    if(!file) return;

    const reader = new FileReader();

    reader.onload = function(event){

        const img = new Image();

        img.onload = function(){

            const ctx = getActiveLayer().ctx;

            ctx.drawImage(img,0,0);

        };

        img.src = event.target.result;

    };

    reader.readAsDataURL(file);

});

// 기본 레이어 생성
createLayer("배경");

</script>

</body>
</html>
728x90

+ Recent posts