초보자를 위한 웹 그림판 만들기(그리기,메모,블럭지정 또는 모양넣기)
오늘은 저번에 이어 웹그림판 만들기에서 좀더 빌드업된 버젼으로
그리기 + 메모 +모양 넣기 또는 블럭을 지정하는 기능을 만듬
나는 그리기 와 메모만 필요하다는 분은 아래 링크 참고
https://cbn1218.tistory.com/56
초보자도 만들 수 있는 자바스크립트로 그림판 만들기(텍스트 추가하기 기능)
저번에 캔버스(canvas)를 이용하여 그림판과 같이 그림 그리기 기능을 만들었음. 혹시 궁금하시다면 아래 링크 참고하시길.... https://cbn1218.tistory.com/25 초보자를 위한 캔버스(Canvas)로 그림 그리기
cbn1218.tistory.com
1.결과
일단 무엇을 만들수 있는지가 제일 궁금하니 먼저 결과 화면 ㄱㄱ
그림판처럼 그리기, 메모, 모양넣기 또는 블럭지정이가능한 기능을 만들수 있음
2.설명
(1)html
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<canvas id="myCanvas" width="500px" height="500px"></canvas>
<div id="memoDiv"></div>
<button id="memo">메모</button>
<button id="draw">그리기</button>
<button id="dragRec">블럭지정</button>
</body>
</html>
html 코드는 의외로 간단함
눈여겨볼거는 canvas 태그를 넣어야 된다는것과
"memoDiv" 태그를 만들어야함.
그리기와 블록지정은 canvas 를 활용하고 메모기능은 memoDiv 를 활용한다.
(2)css
<style>
#myCanvas{
position: absolute;
border: 1px solid black;
z-index: 999;
}
#memoDiv{
width: 500px;
height: 500px;
position: absolute;
border: 1px solid red;
}
#memo{
position: absolute;
left: 510px;
}
#draw{
position: absolute;
top:40px;
left: 510px;
}
#dragRec{
position: absolute;
top:80px;
left: 510px;
}
.text-input{
position: absolute;
}
.text-object{
position: absolute;
cursor: move;
}
</style>
주의깊게 봐야 될 부분은 #myCanvas 스타일에 z-index: 999; 를 지정한 부분인데,
캔버스를 제일상단 레이아웃에 올리고 메모 버튼을 눌렀을때 #memoDiv 태그를 제일 상단에 올려
메모와 그리기를 한번에 사용할 수 있음
즉, 버튼을 누르는거에 따라서 #myCanvas 를 제일상단 레이아웃에 올려서 그리거나 블록을 지정하고
#memoDiv를 제일 상단레이아웃에 올려서 메모기능을 쓸 수 있다.
(3)javascript
<script>
// 컨트롤할 엘리먼트를 취득
const canvas = document.getElementById('myCanvas');
// 2d모드의 그리기 객체를 취득한다. => 이 객체를 통해 canvas에 그림을 그릴 수 있다.
const ctx = canvas.getContext("2d");
const memoDiv = document.getElementById('memoDiv');
const memo = document.getElementById('memo');
const rec = document.getElementById('dragRec');
let painting = false; // 선그리기 시작과 끝을 제어하기 위함
let dragRec = false; // 블럭지정 모드 true or false 선택
let drawLine = false; // 그리기 모드 true or false 선택
let rectangles = []; // 블록을 저장할 배열
let data = []; // 그리기 좌표 저장할 배열
let dataArr = []; //그리는 객체를 각각 저장할때
let stopPainting;
let startPainting;
let onMouseMove;
let startRec;
let moveRec;
let stopRec;
draw.addEventListener('click', function(event) { //그리기 버튼을 누름
dragRec = false; //블럭모드 끄고
drawLine = true; //그리기모드 켜고
canvas.removeEventListener("mousedown",startRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제
canvas.removeEventListener("mousemove",moveRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제
canvas.removeEventListener("mouseup",stopRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제
if(drawLine){ //그리기모드가 ture 일때 아래 이벤트를 걸수 있음
//그리기 색지정
ctx.strokeStyle = 'black';
//그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함
canvas.style.zIndex = 999;
stopPainting = function (){
painting=false;
if(data && data.length > 0){
dataArr[dataArr.length] = data //객체별로 그린 좌표묶음을 배열에 저장
data = [ ] //초기화
}
}
startPainting = function (){
painting=true;
}
onMouseMove =function (e){
//마우스 위치 좌표
var x1 = e.clientX-10 ;
var y1 = e.clientY-10 ;
if(!painting){ // ture 선을 그리기 시작, false 선그리기를 멈춤
ctx.beginPath(); // 새로운 경로 지정을 위한 선언
ctx.moveTo(x1,y1); //선그리기전 좌표를 이동
}else{
ctx.lineTo(x1,y1); //현재 지점에서 (x,y)좌표까지 선으로 그림
ctx.stroke(); //선의 종류를 지정해줌 (외곽선)
data.push({ x: x1, y: y1});
}
}
canvas.addEventListener("mousemove",onMouseMove);//마우스가 움직일때 onMouseMove 이벤트걸기
canvas.addEventListener("mousedown",startPainting);//마우스를 눌렀을때 startPainting 이벤트걸기
canvas.addEventListener("mouseup",stopPainting);//마우스를 떼었을때 stopPainting 이벤트걸기
canvas.addEventListener("mouseleave",stopPainting);//마우스가 벗어났을때 stopPainting 이벤트걸기
}
});
rec.addEventListener('click', function(event) { //블럭지정 버튼을 누름
dragRec = true; //블럭모드 켜고
drawLine = false; //그리기 모드 끄기
canvas.removeEventListener("mousedown",startPainting); //블럭지정 모드일때 그리기모드 이벤트끄기
canvas.removeEventListener("mousemove",onMouseMove); //블럭지정 모드일때 그리기모드 이벤트끄기
canvas.removeEventListener("mouseup",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기
canvas.removeEventListener("mouseleave",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기
if(dragRec){
//그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함
canvas.style.zIndex = 999;
ctx.fillStyle = "rgba(255, 255, 0, 0.3)";
var draw = false; //블럭의 시작과 끝의 제어하기 위한 변수
startRec= function (e){
var startX = parseInt(e.clientX - canvas.getBoundingClientRect().left);
var startY = parseInt(e.clientY - canvas.getBoundingClientRect().top);
var rectangle = {
startX: startX,
startY: startY,
endX: startX,
endY: startY
};
rectangles.push(rectangle);
drawRectangles(); // 모든 네모들을 다시 그림
draw = true;
}
moveRec= function (e){
if(draw){
var endX = parseInt(e.clientX - canvas.getBoundingClientRect().left);
var endY = parseInt(e.clientY - canvas.getBoundingClientRect().top);
rectangles[rectangles.length - 1].endX = endX; //블럭을 지정할때 endX 좌표값을 새로 업데이트
rectangles[rectangles.length - 1].endY = endY; //블럭을 지정할때 endY 좌표값을 새로 업데이트
drawRectangles(); // 모든 네모들을 다시 그림
//앞서 그리기한게 있다면 배열에 있는 좌표값을 꺼내서 다시 그리는 로직
dataArr.forEach(innerArray => {
ctx.beginPath(); //새로운 경로 시작
ctx.moveTo(innerArray[0].x, innerArray[0].y); //선그리기전 좌표시작점
innerArray.forEach(item => {
ctx.lineTo(item.x, item.y); //점과 점을 잇는부분
ctx.stroke();//실제 선 그리는 부분
});
});
}
}
stopRec= function (e){
draw = false;
}
canvas.addEventListener("mousedown",startRec); //마우스를 누를때 startRec 이벤트걸기
canvas.addEventListener("mousemove",moveRec); //마우스 움직일때 moveRec 이벤트 걸기
canvas.addEventListener("mouseup",stopRec); //마우스 뗄때 stopRec 이벤트 걸기
// 모든 네모들을 그리는 함수
function drawRectangles() {
ctx.clearRect(0, 0, canvas.width, canvas.height); //clear canvas
rectangles.forEach(function(rectangle) {
ctx.fillRect(rectangle.startX, rectangle.startY, rectangle.endX - rectangle.startX, rectangle.endY - rectangle.startY);
});
}
}
})
memo.addEventListener('click', function(event) { //메모 버튼을 누름
//메머버튼을 누름과 동시에 메모기능을 사용할수 있는 div박스가 맨위로 올라와 메모기능사용가능
canvas.style.zIndex = 0;
var x = 100;
var y = 100;
//input 박스를 생성하여 글자를 입력받음
var textInput = document.createElement('input');
textInput.type = 'text';
textInput.classList.add('text-input');
textInput.style.left = x + 'px';
textInput.style.top = y + 'px';
//위에 생성한 input 박스를 해당 div에 적용
memoDiv.appendChild(textInput);
textInput.focus();
//마우스가 focus상태가 아닐때 input 박스에서 받은 값을 createTextObject() 함수에 넘겨줌
textInput.addEventListener('blur', function() {
if (textInput.value.trim() !== '') {
createTextObject(textInput.value, x, y);
}
memoDiv.removeChild(textInput); // input 박스는 본인역할을 다끝내서 지움
});
});
//텍스트 div에 대한 이벤트 처리
function createTextObject(text, x, y) {
//위에 input박스를 통해 입력받은 내용을 div를 생성
var textObject = document.createElement('div');
textObject.textContent = text;
textObject.classList.add('text-object');
textObject.style.left = x + 'px';
textObject.style.top = y + 'px';
//생성된 div의 위치에 이벤트가 발생했을때
textObject.addEventListener('mousedown', function(event) {
var offsetX = event.clientX - textObject.offsetLeft;
var offsetY = event.clientY - textObject.offsetTop;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
//마우스가 움직일때 div의 위치를 이동시켜주는 이벤트
function onMouseMove(event) {
var newX = (event.clientX - offsetX - memoDiv.offsetLeft)+10;
var newY = (event.clientY - offsetY - memoDiv.offsetTop)+10;
textObject.style.left = newX + 'px';
textObject.style.top = newY + 'px';
}
//마우스이벤트가 끝났을때 이벤트 리스너 정리 해줌
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
});
// 생성된 div의 내용을 수정할때
textObject.addEventListener('dblclick', function() {
//내용을 수정할때 input을 다시 생성하여 기존내용 및 수정하는 내용을 입력 받음
var originalText = textObject.textContent;
var inputField = document.createElement('input');
inputField.type = 'text';
inputField.value = originalText;
inputField.classList.add('text-input');
inputField.style.left = textObject.style.left;
inputField.style.top = textObject.style.top;
//마우스가 focus상태가 아닐때 input박스에 입력받은 내용을 기존div에 넘겨주고 혹여 내용이 없으면 div를 삭제
inputField.addEventListener('blur', function() {
textObject.textContent = inputField.value;
memoDiv.removeChild(inputField);
//내용이 없으면 div를 삭제하는 부분
if (textObject.textContent.trim() === '') {
memoDiv.removeChild(textObject);
}
});
//위에 설정한 input 박스를 설정해줌
memoDiv.appendChild(inputField);
inputField.focus();
});
//생성한 div를 최종적으로 적용할때
memoDiv.appendChild(textObject);
}
</script>
자바스크립트가 제일 중요함!
상세한설명은 코드마다 설명을 주석처리함.
한줄한줄 보면서 코드를 따라 쳐보면 도움이 될듯함.
(4)전체코드
설명은 필요없고 전체 코드만 필요하다 하시는분은 아래 코드 참고
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<style>
#myCanvas{
position: absolute;
border: 1px solid black;
z-index: 999;
}
#memoDiv{
width: 500px;
height: 500px;
position: absolute;
border: 1px solid red;
}
#memo{
position: absolute;
left: 510px;
}
#draw{
position: absolute;
top:40px;
left: 510px;
}
#dragRec{
position: absolute;
top:80px;
left: 510px;
}
.text-input{
position: absolute;
}
.text-object{
position: absolute;
cursor: move;
}
</style>
<body>
<canvas id="myCanvas" width="500px" height="500px"></canvas>
<div id="memoDiv"></div>
<button id="memo">메모</button>
<button id="draw">그리기</button>
<button id="dragRec">블럭지정</button>
<script>
// 컨트롤할 엘리먼트를 취득
const canvas = document.getElementById('myCanvas');
// 2d모드의 그리기 객체를 취득한다. => 이 객체를 통해 canvas에 그림을 그릴 수 있다.
const ctx = canvas.getContext("2d");
const memoDiv = document.getElementById('memoDiv');
const memo = document.getElementById('memo');
const rec = document.getElementById('dragRec');
let painting = false; // 선그리기 시작과 끝을 제어하기 위함
let dragRec = false; // 블럭지정 모드 true or false 선택
let drawLine = false; // 그리기 모드 true or false 선택
let rectangles = []; // 블록을 저장할 배열
let data = []; // 그리기 좌표 저장할 배열
let dataArr = []; //그리는 객체를 각각 저장할때
let stopPainting;
let startPainting;
let onMouseMove;
let startRec;
let moveRec;
let stopRec;
draw.addEventListener('click', function(event) { //그리기 버튼을 누름
dragRec = false; //블럭모드 끄고
drawLine = true; //그리기모드 켜고
canvas.removeEventListener("mousedown",startRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제
canvas.removeEventListener("mousemove",moveRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제
canvas.removeEventListener("mouseup",stopRec); //그리기모드 일때 이전에 블럭이벤트 걸어놓은거 삭제
if(drawLine){ //그리기모드가 ture 일때 아래 이벤트를 걸수 있음
//그리기 색지정
ctx.strokeStyle = 'black';
//그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함
canvas.style.zIndex = 999;
stopPainting = function (){
painting=false;
if(data && data.length > 0){
dataArr[dataArr.length] = data //객체별로 그린 좌표묶음을 배열에 저장
data = [ ] //초기화
}
}
startPainting = function (){
painting=true;
}
onMouseMove =function (e){
//마우스 위치 좌표
var x1 = e.clientX-10 ;
var y1 = e.clientY-10 ;
if(!painting){ // ture 선을 그리기 시작, false 선그리기를 멈춤
ctx.beginPath(); // 새로운 경로 지정을 위한 선언
ctx.moveTo(x1,y1); //선그리기전 좌표를 이동
}else{
ctx.lineTo(x1,y1); //현재 지점에서 (x,y)좌표까지 선으로 그림
ctx.stroke(); //선의 종류를 지정해줌 (외곽선)
data.push({ x: x1, y: y1});
}
}
canvas.addEventListener("mousemove",onMouseMove);//마우스가 움직일때 onMouseMove 이벤트걸기
canvas.addEventListener("mousedown",startPainting);//마우스를 눌렀을때 startPainting 이벤트걸기
canvas.addEventListener("mouseup",stopPainting);//마우스를 떼었을때 stopPainting 이벤트걸기
canvas.addEventListener("mouseleave",stopPainting);//마우스가 벗어났을때 stopPainting 이벤트걸기
}
});
rec.addEventListener('click', function(event) { //블럭지정 버튼을 누름
dragRec = true; //블럭모드 켜고
drawLine = false; //그리기 모드 끄기
canvas.removeEventListener("mousedown",startPainting); //블럭지정 모드일때 그리기모드 이벤트끄기
canvas.removeEventListener("mousemove",onMouseMove); //블럭지정 모드일때 그리기모드 이벤트끄기
canvas.removeEventListener("mouseup",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기
canvas.removeEventListener("mouseleave",stopPainting); //블럭지정 모드일때 그리기모드 이벤트끄기
if(dragRec){
//그리기버튼을 눌르면 캔버스가 젤위에 레이아웃을 배치하므로써 캔버스 기능을 사용함
canvas.style.zIndex = 999;
ctx.fillStyle = "rgba(255, 255, 0, 0.3)";
var draw = false; //블럭의 시작과 끝의 제어하기 위한 변수
startRec= function (e){
var startX = parseInt(e.clientX - canvas.getBoundingClientRect().left);
var startY = parseInt(e.clientY - canvas.getBoundingClientRect().top);
var rectangle = {
startX: startX,
startY: startY,
endX: startX,
endY: startY
};
rectangles.push(rectangle);
drawRectangles(); // 모든 네모들을 다시 그림
draw = true;
}
moveRec= function (e){
if(draw){
var endX = parseInt(e.clientX - canvas.getBoundingClientRect().left);
var endY = parseInt(e.clientY - canvas.getBoundingClientRect().top);
rectangles[rectangles.length - 1].endX = endX; //블럭을 지정할때 endX 좌표값을 새로 업데이트
rectangles[rectangles.length - 1].endY = endY; //블럭을 지정할때 endY 좌표값을 새로 업데이트
drawRectangles(); // 모든 네모들을 다시 그림
//앞서 그리기한게 있다면 배열에 있는 좌표값을 꺼내서 다시 그리는 로직
dataArr.forEach(innerArray => {
ctx.beginPath(); //새로운 경로 시작
ctx.moveTo(innerArray[0].x, innerArray[0].y); //선그리기전 좌표시작점
innerArray.forEach(item => {
ctx.lineTo(item.x, item.y); //점과 점을 잇는부분
ctx.stroke();//실제 선 그리는 부분
});
});
}
}
stopRec= function (e){
draw = false;
}
canvas.addEventListener("mousedown",startRec); //마우스를 누를때 startRec 이벤트걸기
canvas.addEventListener("mousemove",moveRec); //마우스 움직일때 moveRec 이벤트 걸기
canvas.addEventListener("mouseup",stopRec); //마우스 뗄때 stopRec 이벤트 걸기
// 모든 네모들을 그리는 함수
function drawRectangles() {
ctx.clearRect(0, 0, canvas.width, canvas.height); //clear canvas
rectangles.forEach(function(rectangle) {
ctx.fillRect(rectangle.startX, rectangle.startY, rectangle.endX - rectangle.startX, rectangle.endY - rectangle.startY);
});
}
}
})
memo.addEventListener('click', function(event) { //메모 버튼을 누름
//메머버튼을 누름과 동시에 메모기능을 사용할수 있는 div박스가 맨위로 올라와 메모기능사용가능
canvas.style.zIndex = 0;
var x = 100;
var y = 100;
//input 박스를 생성하여 글자를 입력받음
var textInput = document.createElement('input');
textInput.type = 'text';
textInput.classList.add('text-input');
textInput.style.left = x + 'px';
textInput.style.top = y + 'px';
//위에 생성한 input 박스를 해당 div에 적용
memoDiv.appendChild(textInput);
textInput.focus();
//마우스가 focus상태가 아닐때 input 박스에서 받은 값을 createTextObject() 함수에 넘겨줌
textInput.addEventListener('blur', function() {
if (textInput.value.trim() !== '') {
createTextObject(textInput.value, x, y);
}
memoDiv.removeChild(textInput); // input 박스는 본인역할을 다끝내서 지움
});
});
//텍스트 div에 대한 이벤트 처리
function createTextObject(text, x, y) {
//위에 input박스를 통해 입력받은 내용을 div를 생성
var textObject = document.createElement('div');
textObject.textContent = text;
textObject.classList.add('text-object');
textObject.style.left = x + 'px';
textObject.style.top = y + 'px';
//생성된 div의 위치에 이벤트가 발생했을때
textObject.addEventListener('mousedown', function(event) {
var offsetX = event.clientX - textObject.offsetLeft;
var offsetY = event.clientY - textObject.offsetTop;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
//마우스가 움직일때 div의 위치를 이동시켜주는 이벤트
function onMouseMove(event) {
var newX = (event.clientX - offsetX - memoDiv.offsetLeft)+10;
var newY = (event.clientY - offsetY - memoDiv.offsetTop)+10;
textObject.style.left = newX + 'px';
textObject.style.top = newY + 'px';
}
//마우스이벤트가 끝났을때 이벤트 리스너 정리 해줌
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
});
// 생성된 div의 내용을 수정할때
textObject.addEventListener('dblclick', function() {
//내용을 수정할때 input을 다시 생성하여 기존내용 및 수정하는 내용을 입력 받음
var originalText = textObject.textContent;
var inputField = document.createElement('input');
inputField.type = 'text';
inputField.value = originalText;
inputField.classList.add('text-input');
inputField.style.left = textObject.style.left;
inputField.style.top = textObject.style.top;
//마우스가 focus상태가 아닐때 input박스에 입력받은 내용을 기존div에 넘겨주고 혹여 내용이 없으면 div를 삭제
inputField.addEventListener('blur', function() {
textObject.textContent = inputField.value;
memoDiv.removeChild(inputField);
//내용이 없으면 div를 삭제하는 부분
if (textObject.textContent.trim() === '') {
memoDiv.removeChild(textObject);
}
});
//위에 설정한 input 박스를 설정해줌
memoDiv.appendChild(inputField);
inputField.focus();
});
//생성한 div를 최종적으로 적용할때
memoDiv.appendChild(textObject);
}
</script>
</body>
</html>
웹캔버스 누가 쓰냐 하지만 프론트엔드 개발자들한테 좌표개념을 알수있는 기능이므로
알아두면 플젝때 도움될거 같음.
그럼...끝....