반응형

새로운 프로젝트를 들어가면서 새로운 텍스트 에디터를 적용해보기 위해 이것저것 찾던 도중 IE에서 잘 동작이되며(이놈의 IE...) 무료 라이센스인 것들을 고르고 고르다보니 기능이 없거나 이쁘지가 않거나 여러 이슈들이 있었는데, 만족할만한 에디터를 찾아서 공유해보고자 합니다.

 

텍스트 에디터 종류들은 WYSIWYG HTML Editor 형태로 검색해보시면 더 많은 에디터를 찾아 볼 수 있습니다.

 

TinyMce License로서 특별히 추가하는 plugin을 사용하지 않는다는 전제하에 무료입니다.

https://www.tiny.cloud/blog/tinymce-free-wysiwyg-html-editor/

 

The TinyMCE WYSIWYG HTML editor is free forever

Looking for a free WYSIWYG HTML editor? The TinyMCE core editor is free to use for commercial and noncommercial purposes. Fully functional with 44 plugins and ready to customize as needed.

www.tiny.cloud

 

Tiny Editor

https://www.tiny.cloud/docs/quick-start/

 

TinyMCE | Quick start

Get an instance of TinyMCE 5 up and running using the Tiny Cloud.

www.tiny.cloud

 

해당 글을 보고 먼저 테스트용 계정을 생성해서 api key를 받고 간단하게 http서버를 열어서 소스코드 테스트를 진행해보았습니다.

 

뭐 간단한 세팅은 진짜 간단합니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>

    <script>
      tinymce.init({
        selector: '#mytextarea'
      });
    </script>

  </head>

  <body>
  <h1>TinyMCE Quick Start Guide</h1>
    <form method="post">
      <textarea id="mytextarea">Hello, World!</textarea>
    </form>
  </body>
</html>

간단하게 에디터의 모습으로 보입니다.

 

하지만 좀 더 블로그에서 사용하는 에디터나 이쁘게 커스텀을 하고 싶어서 여러 옵션을 통해 좀 더 변경을 해보았습니다.

 

 

개인적으로 변경한 Tiny Editor

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.tiny.cloud/1/각자 발급받은 api key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body>
    <h1>TinyMCE Quick Start Guide</h1>
    <form method="post">
        <textarea id="editor">Hello, World!</textarea>
        <button type="button" id="save">게시글 저장</button>
    </form>
</body>

<script>
$(function(){
    var plugins = [
        "advlist", "autolink", "lists", "link", "image", "charmap", "print", "preview", "anchor",
        "searchreplace", "visualblocks", "code", "fullscreen", "insertdatetime", "media", "table",
        "paste", "code", "help", "wordcount", "save"
    ];
    var edit_toolbar = 'formatselect fontselect fontsizeselect |'
               + ' forecolor backcolor |'
               + ' bold italic underline strikethrough |'
               + ' alignjustify alignleft aligncenter alignright |'
               + ' bullist numlist |'
               + ' table tabledelete |'
               + ' link image';

    tinymce.init({
    	language: "ko_KR", //한글판으로 변경
        selector: '#editor',
        height: 500,
        menubar: false,
        plugins: plugins,
        toolbar: edit_toolbar,
        
        /*** image upload ***/
        image_title: true,
        /* enable automatic uploads of images represented by blob or data URIs*/
        automatic_uploads: true,
        /*
            URL of our upload handler (for more details check: https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_url)
            images_upload_url: 'postAcceptor.php',
            here we add custom filepicker only to Image dialog
        */
        file_picker_types: 'image',
        /* and here's our custom image picker*/
        file_picker_callback: function (cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');

            /*
            Note: In modern browsers input[type="file"] is functional without
            even adding it to the DOM, but that might not be the case in some older
            or quirky browsers like IE, so you might want to add it to the DOM
            just in case, and visually hide it. And do not forget do remove it
            once you do not need it anymore.
            */
            input.onchange = function () {
                var file = this.files[0];

                var reader = new FileReader();
                reader.onload = function () {
                    /*
                    Note: Now we need to register the blob in TinyMCEs image blob
                    registry. In the next release this part hopefully won't be
                    necessary, as we are looking to handle it internally.
                    */
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);

                    /* call the callback and populate the Title field with the file name */
                    cb(blobInfo.blobUri(), { title: file.name });
                };
                reader.readAsDataURL(file);
            };
            input.click();
        },
        /*** image upload ***/
        
        content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
    });


    $("#save").on("click", function(){
        var content = tinymce.activeEditor.getContent();
        console.log(content);
    });
    
});
</script>
</html>

* tiny 동작 script를 적용시 저는 개인적으로 발급받은 url주소를 입력하였습니다. 자신만의 api key를 발급받은 url로 연결하시면 될 것 같습니다.

 

변경 후에는 좀 더 깔끔하게 메뉴바를 지우고 툴바들로 위치시켰고, 필요한 테이블표, 이미지, 링크등을 추가하였습니다.

텍스트 제어 관련된 색상, 폰트, 컬러 등을 순차적으로 배치해보았습니다. 또한, language 옵션에서 한글번역이 가능하여 변경하였습니다.

 

form태그 내부에 게시글 저장이라는 button태그를 추가하였는데, 클릭하면 tiny자체에서 제공하는 getContent메소드를 통해 게시글 내부의 HTML 코드를 확인 할 수 있습니다.

 

해당 내용을 서버로 전송하여 저장하는 형태로 처리하면 될 것 같습니다.

 

 

 

여러가지 옵션 변경은 아래글을 참고하여주세요.

https://www.tiny.cloud/docs/

 

TinyMCE | Documentation

Official documentation for the most advanced and widely deployed rich text editor platform.

www.tiny.cloud

 

반응형
반응형

UI를 작성하다보면 input 박스 안에 버튼 이미지를 위치시키고 입력받은 값에 따라 보여주고 사라지고 제어등을 하게 되는 경우가 있다.

 

조건은 아래와 같다.

1. 입력하고자 하는 input에 focus를 잃는 경우(blur 발생) 버튼을 사라지게 하는 이벤트를 추가

2. 입력하는 input에 focus를 얻은 상태에서 입력중이면 버튼이 활성화

3. 활성화된 버튼을 클릭하면 input의 데이터가 다 지워짐

 

 

문제는 click보다 blur가 더 빨리 동작한다는 것이다.

 

stackoverflow의 글 덕분에 꼼수로 간단하게 해결하였다.

https://stackoverflow.com/questions/10652852/jquery-fire-click-before-blur-event

 

jQuery: fire click() before blur() event

I have an input field, where I try to make autocomplete suggestion. Code looks like

 
On input's blur() event I want to...

 

stackoverflow.com

 

 

click대신 mousedown 이벤트를 사용하였다.

 

아래는 실제 동작시킨 스크립트 코드이다.

$('.inp_box').on("mousedown", ".inp_remove_btn", function(){ //click 사용안됨 //blur가 더 빨리 동작함
    $(this).siblings("input").val("");

}).on("change keyup input", "input", function(e){ //입력이 시작되면 삭제버튼 활성화
    $(this).siblings(".inp_remove_btn").addClass("active");

}).on("blur", "input", function(e){ //input focus를 잃으면 삭제버튼 비활성화
    $(this).siblings(".inp_remove_btn").removeClass("active");

}).on("focus", "input", function(e){ //input focus를 얻으면 길이 체크하여 삭제버튼 활성화 유무체크
    if($(this).val().length > 0){
        $(this).siblings(".inp_remove_btn").addClass("active");
    }
});
반응형
반응형

새로운 전자정부 프레임워크를 설치하고 구동하면서 과거 소스가 돌아가지 않는 현상이 발생했다.

자바 버전도 맞춰주고 오류날만한것도 다 잡고 Maven - Udate Project... 처리까지 했지만 계속해서 오류가 발생했는데, 원인은 자바11 버전으로 기본 세팅이 되어있으면서 발생하는 오류였고 pom.xml에 아래 내용을 추가하고 해결되었다.

 

 

 

자바11 pom.xml 오류 수정

pom.xml

<!-- 자바11 이슈 -->
<dependency>
	<groupId>javax.xml.bind</groupId>
	<artifactId>jaxb-api</artifactId>
	<version>2.3.1</version>
</dependency>
<dependency>
	<groupId>com.sun.xml.bind</groupId>
	<artifactId>jaxb-core</artifactId>
	<version>2.3.0.1</version>
</dependency>
<dependency>
	<groupId>com.sun.xml.bind</groupId>
	<artifactId>jaxb-impl</artifactId>
	<version>2.3.1</version>
</dependency>
<!-- 자바11 이슈 -->

 

 

 

Maven 저장소 위치변경

사실 이렇게하고 구동하면 또 되어야겠지만, maven 저장소인 .m2 - repogitory가 꼬인경우도 있다.

이미 너무 많은 프로젝트들끼리 같은 저장소를 바라보고 있어서 해당 전자정부프레임워크는 다른곳을 바라보도록 했다.

일반적으로 아래 경로를 바라보고 있을텐데,

C:\Users\사용자명\.m2\repository

 

.m2 repository_egov3.10(원하는 디렉토리 명으로 만든다)디렉토리에 를 추가하고 settings.xml을 만든다.


settings.xml

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    
	<localRepository>C:\Users\사용자\.m2\repository_egov3.10</localRepository>
	<interactiveMode>true</interactiveMode>
	<offline>false</offline>
</settings>

 

다 만들었으면 이클립스로 돌아가서 Window - Preferences

Maven - User Settings로 가서 User Settings에 방금 만든 xml을 연결한다.

 

그럼 Maven이 새로 받기 시작한다.

 

 

 

톰캣 재연결

다 끝났으면 기존 Tomcat은 삭제하고 꼭 다시 생성해서 연결해주고, 아래 설정을 추가한다.

프로젝트 우클릭 - Buil Path - Configure Build Path...

 

추가하면 Apach Tomcat 버전이 뜬다.

반응형
반응형

리액트를 공부하면서 객체 불변성을 지켜야한다는 컨셉을 지키기 위해 좀 더 확실하게 개념을 잡을 필요가 있어서 공부하다가 정리를 해봅니다.

자바스크립트에서 객체나 배열을 복사할때 단순하게 대입연산자를 통해 처리하면 아래와 같이 같은 주소값을 바라보게 된다.

 

대입연산자를 통한 복사(참조 복사)

let obj1 = {a:1, b:2};
let obj2 = obj1;
obj2.a = 3;
console.log(obj1.a === obj2.a) //true 같은 주소값을 바라보고 있어서 같이 값이 바뀌어있다.
console.log(obj1.a) //3

obj1 생성하면서 발생한 주소값을 obj2도 바라보게 되어 값이 같이 바뀌게 된다.

즉, obj1과 obj2는 그냥 같은 값이다.

 

 

얕은 복사

ex1)

let obj1 = {a:1, b:2};
let obj2 = {...obj1};
console.log(obj1 === obj2) //false 서로 같은 주소값을 바라보진 않는다.
obj2.a = 3;
console.log(obj1.a === obj2.a) //false
console.log(obj1.a) //1

es6 문법 중 전개 연산자(...obj)를 통해 얕은 복사를 진행하였다.

 

결과만 봤을때는?

깊은 복사가 진행되었다. 단순 배열이나 단순 객체의 경우에는 깊은 복사가 진행된다.

각각의 객체가 서로 다른 값을 유지하기 때문이다.

하지만 아래와 같이 배열안에 객체들이 존재하는 경우에는 어떨까?

 

ex2)

let obj1 = [{a:1, b:2}, {a:5, b:25}];
let obj2 = [...obj1];
console.log(obj1 === obj2) //false 서로 같은 주소값을 바라보진 않는다.
obj2[0].a = 3;
console.log(obj1[0].a === obj2[0].a) //true
console.log(obj1[0].a) //3

배열안에 각각 객체들이 들어간 obj1을 복사하였다.

obj1, obj2의 배열만 비교하였을땐 서로 다른 주소값을 보도록 된 것을 볼 수 있지만, 첫번째 배열 인자의 a값을 확인해보면 true로 발생한다.

첫번쨰 인자의 객체주소값을 동일하게 obj1[0].a 와 obj2[0].a가 공유하고 있기때문이다.

마지막행에서도 바꾼적 없는 obj1[0].a 값이 3으로 나오는것을 볼 수 있다.

 

이처럼 내부의 객체나 배열등이 원본의 값을 참조하는 경우 얕은 복사(shallow copy)라고 지칭한다.

 

 

깊은 복사

let obj1 = [{a:1, b:2}, {a:5, b:25}];
let obj2 = [...obj1];
console.log(obj1 === obj2) //false 서로 같은 주소값을 바라보진 않는다.
obj2[0] = {...obj2[0],
        a : 3}; //전개 연산자를 통해 내부 특정 속성값까지 참조값을 변경한다.
console.log(obj1[0].a === obj2[0].a) //false 내부 객체의 참조값까지 변경되었다.
console.log(obj1[0].a) //1

깊은 복사의 예제이다.

 

다른 방식도 있지만 이번에도 전개 연산자(...)를 통해 처리하였다.

 

이부분이 포인트일 것이다.

obj2[0] = {...obj2[0],
        a : 3};

변경하고자 하는 요소의 참조값을 변경하여 내부까진 변경을 해야한다.

 

 

물론 아래처럼 다른 요소는 당연히 얕은 복사가 되었을 것이다.

console.log(obj1[0].b === obj2[0].b) //true

실제로 값이 필요한부분만 깊은 복사처리를 한것이고, 애초에 배열이나 객체 내부의 객체등을 깊은 복사를 하고자 할때는 JSON.stringify() 메소드는 또는 반복문을 통해 재처리가 필요할 것이다.

반응형
반응형

beginShape를 통해 하트 모양의 도형을 그린 후 빙글빙글 회전하면서 이동하는 예제입니다.

See the Pen p5.js Heart moving and rotates by myhappyman (@myhappyman) on CodePen.

 

해당 예제를 만들어보면서 발생했던 문제점과 해결방법을 공유해 봅니다.

x, y 값을 통해 기본도형(원, 사각형)등의 생성 위치를 변경하면서 이동하는 애니메이션을 처리 예제를 먼저 만들어봤었는데, 이때는 큰 문제가 없었습니다.

추가적으로 회전 기능을 추가하면서 문제가 발생했습니다.

중심점이 계속 변경되면서 깜빡거리는 현상이 발생하는데, 일반 도형의 경우 모드가 존재하여 rectMode(CENTER)를 처리 후 rotate()와 좌표 변경으로 애니메이션을 처리하였습니다.

 

하트모양을 만들기 위해 beginShape()를 통해 하트를 구성하고 그려냈는데, 중심점을 고정하는 메소드가 존재하지 않았습니다. 이상태에서 회전과 이동을 생성점을 통해 처리하게 되면 빙글빙글 돌면서 정상적으로 돌지 않는걸 볼 수 있습니다.

이런 현상을 방지하기 위해 중앙점을 고정으로 (0, 0) 처리하였고, 이동은 translate() 메소드를 통해 이동 좌표를 별개로 처리하여 해결하였습니다.

 

아래는 작성한 샘플소스이며 ES6 문법으로 작성되었습니다.

class Heart{
    constructor(){
        this.x = 0;
        this.y = 0;
        this.mx = random(0, width);
        this.my = random(0, height);
        this.speedRange = [-2,-1,1,2];
        this.xSpeed = random(this.speedRange);
        this.ySpeed = random(this.speedRange);
        this.size = random(55);
        this.degree = random(10);

        this.red = random(255);
        this.green = random(255);
        this.blue = random(255);
    }

    createHeart(){
        push();
        translate(this.mx, this.my);
        rotate(PI * this.degree);

        noStroke();
        noSmooth();
        fill(this.red, this.green, this.blue);
        beginShape();
        vertex(this.x, this.y);
        bezierVertex(this.x - this.size / 2, this.y - this.size / 2, this.x - this.size, this.y + this.size / 3, this.x, this.y + this.size);
        bezierVertex(this.x + this.size, this.y + this.size / 3, this.x + this.size / 2, this.y - this.size / 2, this.x, this.y);            
        endShape(CLOSE);
        pop();
    }

    move(){
        if(this.mx < 0 || this.mx > width){
            this.xSpeed *= -1;
        }
        if(this.my < 0 || this.my > height){
            this.ySpeed *= -1; 
        }

        this.mx += this.xSpeed;
        this.my += this.ySpeed;
        this.degree = this.degree + 0.01;
    }       
}

let heartArr = [];
function setup(){
    var w = window.innerWidth
    var h = window.innerHeight
    createCanvas(w, h);

    for(i=0; i<150; i++){
        heartArr.push(new Heart());
    }
}

function draw(){
    background(11);

    heartArr.forEach(function(r){
        r.createHeart();
        r.move();   
    });        
}

 

 

반응형
반응형

대한민국 권역을 선택 후 지역 특별시나 도를 선택 마지막에 시군구를 선택하는 select박스 예제입니다.

See the Pen Korea Area select Control by myhappyman (@myhappyman) on CodePen.

 

version1.js

관련 js는 아래와 같으며 areaSelectMaker에 제일 부모가 되는 select태그 값을 넣어주면 됩니다.

$(function(){
    areaSelectMaker("select[name=addressRegion]");
});

var areaSelectMaker = function(target){
    if(target == null || $(target).length == 0){
        console.warn("Unkwon Area Tag");
        return;
    }

    var area = {
        "수도권" :{
            "서울특별시" : [ "강남구", "강동구", "강북구", "강서구", "관악구", "광진구", "구로구", "금천구", "노원구", "도봉구", "동대문구", "동작구", "마포구", "서대문구", "서초구", "성동구", "성북구", "송파구", "양천구", "영등포구", "용산구", "은평구", "종로구", "중구", "중랑구" ],
            "경기도" : [ "수원시 장안구", "수원시 권선구", "수원시 팔달구", "수원시 영통구", "성남시 수정구", "성남시 중원구", "성남시 분당구", "의정부시", "안양시 만안구", "안양시 동안구", "부천시", "광명시", "평택시", "동두천시", "안산시 상록구", "안산시 단원구", "고양시 덕양구", "고양시 일산동구",
                "고양시 일산서구", "과천시", "구리시", "남양주시", "오산시", "시흥시", "군포시", "의왕시", "하남시", "용인시 처인구", "용인시 기흥구", "용인시 수지구", "파주시", "이천시", "안성시", "김포시", "화성시", "광주시", "양주시", "포천시", "여주시", "연천군", "가평군",
                "양평군" ],
            "인천광역시" : [ "계양구", "미추홀구", "남동구", "동구", "부평구", "서구", "연수구", "중구", "강화군", "옹진군" ]			
        },
        "강원권" :{
            "강원도" : [ "춘천시", "원주시", "강릉시", "동해시", "태백시", "속초시", "삼척시", "홍천군", "횡성군", "영월군", "평창군", "정선군", "철원군", "화천군", "양구군", "인제군", "고성군", "양양군" ]			
        },
        "충청권" :{
            "충청북도" : [ "청주시 상당구", "청주시 서원구", "청주시 흥덕구", "청주시 청원구", "충주시", "제천시", "보은군", "옥천군", "영동군", "증평군", "진천군", "괴산군", "음성군", "단양군" ],
            "충청남도" : [ "천안시 동남구", "천안시 서북구", "공주시", "보령시", "아산시", "서산시", "논산시", "계룡시", "당진시", "금산군", "부여군", "서천군", "청양군", "홍성군", "예산군", "태안군" ],
            "대전광역시" : [ "대덕구", "동구", "서구", "유성구", "중구" ],
            "세종특별자치시" : [ "세종특별자치시" ]			
        },
        "전라권" :{
            "전라북도" : [ "전주시 완산구", "전주시 덕진구", "군산시", "익산시", "정읍시", "남원시", "김제시", "완주군", "진안군", "무주군", "장수군", "임실군", "순창군", "고창군", "부안군" ],
            "전라남도" : [ "목포시", "여수시", "순천시", "나주시", "광양시", "담양군", "곡성군", "구례군", "고흥군", "보성군", "화순군", "장흥군", "강진군", "해남군", "영암군", "무안군", "함평군", "영광군", "장성군", "완도군", "진도군", "신안군" ],
            "광주광역시" : [ "광산구", "남구", "동구", "북구", "서구" ]			
        },
        "경상권" : {
            "경상북도" : [ "포항시 남구", "포항시 북구", "경주시", "김천시", "안동시", "구미시", "영주시", "영천시", "상주시", "문경시", "경산시", "군위군", "의성군", "청송군", "영양군", "영덕군", "청도군", "고령군", "성주군", "칠곡군", "예천군", "봉화군", "울진군", "울릉군" ],
            "경상남도" : [ "창원시 의창구", "창원시 성산구", "창원시 마산합포구", "창원시 마산회원구", "창원시 진해구", "진주시", "통영시", "사천시", "김해시", "밀양시", "거제시", "양산시", "의령군", "함안군", "창녕군", "고성군", "남해군", "하동군", "산청군", "함양군", "거창군", "합천군" ],
            "부산광역시" : [ "강서구", "금정구", "남구", "동구", "동래구", "부산진구", "북구", "사상구", "사하구", "서구", "수영구", "연제구", "영도구", "중구", "해운대구", "기장군" ],
            "대구광역시" : [ "남구", "달서구", "동구", "북구", "서구", "수성구", "중구", "달성군" ],
            "울산광역시" : [ "남구", "동구", "북구", "중구", "울주군" ]			
        },
        "제주권" : {
            "제주특별자치도" : [ "서귀포시", "제주시" ]			
        }
    };

    for(i=0; i<$(target).length; i++){
        (function(z){
            var a1 = $(target).eq(z);
            var a2 = a1.next();
            var a3 = a2.next();

            //초기화
            init(a1, true);

            //권역 기본 생성
            var areaKeys1 = Object.keys(area);
            areaKeys1.forEach(function(Region){
                a1.append("<option value="+Region+">"+Region+"</option>");
            });

            //변경 이벤트
            $(a1).on("change", function(){
                init($(this), false);
                var Region = $(this).val();
                var keys = Object.keys(area[Region]);
                keys.forEach(function(Do){
                    a2.append("<option value="+Do+">"+Do+"</option>");    
                });
            });

            $(a2).on("change", function(){
                a3.empty().append("<option value=''>선택</option>");
                var Region = a1.val();
                var Do = $(this).val();
                var keys = Object.keys(area[Region][Do]);
                keys.forEach(function(SiGunGu){
                    a3.append("<option value="+area[Region][Do][SiGunGu]+">"+area[Region][Do][SiGunGu]+"</option>");    
                });
            });
        })(i);        

        function init(t, first){
            first ? t.empty().append("<option value=''>선택</option>") : "";
            t.next().empty().append("<option value=''>선택</option>");
            t.next().next().empty().append("<option value=''>선택</option>");
        }
    }
}

 

 

version2.js

2가지 버전으로 만들었는데 아래는 유니크한 id별로 구성하였을 경우 또는, UI상 select 태그가  서로 떨어져 있는 경우 위 소스로 동작하지 않을 경우를 대비하여 만들었습니다.

$(function(){
    areaSelectMaker("#addressRegion1", "#addressDo1", "#addressSiGunGu1");
    areaSelectMaker("#addressRegion2", "#addressDo2", "#addressSiGunGu2");
});

var areaSelectMaker = function(a1, a2, a3){
    if(a1 == null || a2 == null || a3 == null){
        console.warn("Unkwon Area Tag");
        return;
    }

    var area = {
        "수도권" :{
            "서울특별시" : [ "강남구", "강동구", "강북구", "강서구", "관악구", "광진구", "구로구", "금천구", "노원구", "도봉구", "동대문구", "동작구", "마포구", "서대문구", "서초구", "성동구", "성북구", "송파구", "양천구", "영등포구", "용산구", "은평구", "종로구", "중구", "중랑구" ],
            "경기도" : [ "수원시 장안구", "수원시 권선구", "수원시 팔달구", "수원시 영통구", "성남시 수정구", "성남시 중원구", "성남시 분당구", "의정부시", "안양시 만안구", "안양시 동안구", "부천시", "광명시", "평택시", "동두천시", "안산시 상록구", "안산시 단원구", "고양시 덕양구", "고양시 일산동구",
                "고양시 일산서구", "과천시", "구리시", "남양주시", "오산시", "시흥시", "군포시", "의왕시", "하남시", "용인시 처인구", "용인시 기흥구", "용인시 수지구", "파주시", "이천시", "안성시", "김포시", "화성시", "광주시", "양주시", "포천시", "여주시", "연천군", "가평군",
                "양평군" ],
            "인천광역시" : [ "계양구", "미추홀구", "남동구", "동구", "부평구", "서구", "연수구", "중구", "강화군", "옹진군" ]			
        },
        "강원권" :{
            "강원도" : [ "춘천시", "원주시", "강릉시", "동해시", "태백시", "속초시", "삼척시", "홍천군", "횡성군", "영월군", "평창군", "정선군", "철원군", "화천군", "양구군", "인제군", "고성군", "양양군" ]			
        },
        "충청권" :{
            "충청북도" : [ "청주시 상당구", "청주시 서원구", "청주시 흥덕구", "청주시 청원구", "충주시", "제천시", "보은군", "옥천군", "영동군", "증평군", "진천군", "괴산군", "음성군", "단양군" ],
            "충청남도" : [ "천안시 동남구", "천안시 서북구", "공주시", "보령시", "아산시", "서산시", "논산시", "계룡시", "당진시", "금산군", "부여군", "서천군", "청양군", "홍성군", "예산군", "태안군" ],
            "대전광역시" : [ "대덕구", "동구", "서구", "유성구", "중구" ],
            "세종특별자치시" : [ "세종특별자치시" ]			
        },
        "전라권" :{
            "전라북도" : [ "전주시 완산구", "전주시 덕진구", "군산시", "익산시", "정읍시", "남원시", "김제시", "완주군", "진안군", "무주군", "장수군", "임실군", "순창군", "고창군", "부안군" ],
            "전라남도" : [ "목포시", "여수시", "순천시", "나주시", "광양시", "담양군", "곡성군", "구례군", "고흥군", "보성군", "화순군", "장흥군", "강진군", "해남군", "영암군", "무안군", "함평군", "영광군", "장성군", "완도군", "진도군", "신안군" ],
            "광주광역시" : [ "광산구", "남구", "동구", "북구", "서구" ]			
        },
        "경상권" : {
            "경상북도" : [ "포항시 남구", "포항시 북구", "경주시", "김천시", "안동시", "구미시", "영주시", "영천시", "상주시", "문경시", "경산시", "군위군", "의성군", "청송군", "영양군", "영덕군", "청도군", "고령군", "성주군", "칠곡군", "예천군", "봉화군", "울진군", "울릉군" ],
            "경상남도" : [ "창원시 의창구", "창원시 성산구", "창원시 마산합포구", "창원시 마산회원구", "창원시 진해구", "진주시", "통영시", "사천시", "김해시", "밀양시", "거제시", "양산시", "의령군", "함안군", "창녕군", "고성군", "남해군", "하동군", "산청군", "함양군", "거창군", "합천군" ],
            "부산광역시" : [ "강서구", "금정구", "남구", "동구", "동래구", "부산진구", "북구", "사상구", "사하구", "서구", "수영구", "연제구", "영도구", "중구", "해운대구", "기장군" ],
            "대구광역시" : [ "남구", "달서구", "동구", "북구", "서구", "수성구", "중구", "달성군" ],
            "울산광역시" : [ "남구", "동구", "북구", "중구", "울주군" ]			
        },
        "제주권" : {
            "제주특별자치도" : [ "서귀포시", "제주시" ]			
        }
    };

    //초기화
    init(true, true);

    //권역 기본 생성
    var areaKeys1 = Object.keys(area);
    areaKeys1.forEach(function(Region){
        $(a1).append("<option value="+Region+">"+Region+"</option>");
    });

    //변경 이벤트
    $(document).on("change", a1, function(){
        init(false, true);
        var Region = $(this).val();
        var keys = Object.keys(area[Region]);
        keys.forEach(function(Do){
            $(a2).append("<option value="+Do+">"+Do+"</option>");    
        });
    }).on("change", a2, function(){
        init();
        var Region = $(a1).val();
        var Do = $(this).val();
        var keys = Object.keys(area[Region][Do]);
        keys.forEach(function(SiGunGu){
            $(a3).append("<option value="+area[Region][Do][SiGunGu]+">"+area[Region][Do][SiGunGu]+"</option>");    
        });
    });

    function init(first, second){
        first ? $(a1).empty().append("<option value=''>선택</option>") : "";
        second ? $(a2).empty().append("<option value=''>선택</option>") : "";
        $(a3).empty().append("<option value=''>선택</option>");
    }
}
반응형
반응형

3D 정육면체 박스 제어하기

P5.js 사용하여 면에 이미지를 넣고 회전을 시키며 마우스에 따라 회전되도록 제어해보겠습니다.

 

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./p5.min.js"></script>
    <title>Document</title>
</head>
<body>
    <div class="controller">
        <div>
            W : <input type="range" name="width" value="120" step="10" min="10" max="250"><br/>
            H : <input type="range" name="height" value="120" step="10" min="10" max="250"><br/>
            D : <input type="range" name="depth" value="120" step="10" min="10" max="250"><br/>
        </div>
        <div>
            <button type="button" id="rotate">회전</button>
            <button type="button" id="stop">멈춤</button>
        </div>
    </div>
</body>
</html>

 

p5.js

 

var size = [];
var sizeName = ["width", "height", "depth"];
var img;
var rotate = true;
var rotateValue = 1;


window.onload = function(){
    sizeName.forEach(function(s, i){
        size[i] = document.querySelector("input[name="+s+"]").value;
        document.querySelector("input[name="+s+"]").addEventListener("change", function(){
            size[i] = this.value;
        });
    });

    document.getElementById("rotate").addEventListener("click", function(){
        rotate = true;
    });

    document.getElementById("stop").addEventListener("click", function(){
        rotate = false;
    });
}

function preload(){
    img = loadImage("image/ggome.jpg");
}

function setup(){
    createCanvas(710, 400, WEBGL);
    img.loadPixels();
}

function draw() {
    //배경색상
    background(50);

    //질감
    specularMaterial(183, 240, 177);

    //빛
    ambientLight(255, 255, 255);
    directionalLight(50, 50, 50, -frameCount, -frameCount, -1);
    noStroke();

    //마우스 컨트롤 - z축은 고정(Scroll 방지)
    orbitControl(1,1,0);    

    //박스 생성 및 회전
    push();
    if(rotate){
        rotateValue++;
        if(rotateValue > 10000000){
            rotateValue = 1;
        }
    }
    rotateX(rotateValue * 0.01);
    rotateY(rotateValue * 0.01);
    texture(img);
    box(size[0], size[1], size[2]);
    pop();
}

* 이미지 로드기능에서 교차출처(Cors) 오류가 발생하므로 서버에서 구동하셔야합니다.

 

range바에 따라 정육면체로 생성된 박스가 사이즈가 변경되는 모습을 볼 수 있고, 회전, 멈춤 버튼에 따라 회전이 멈추기도 재시작되기도 하는 모습을 볼 수 있습니다.

 

p5 메소드들이 워낙 잘 만들어져 있어서 간단하게 몇개의 메소드만으로 이러한 기능이 만들어지는 것을 볼 수 있습니다.

 

기능을 정리해보겠습니다.

  1. 먼저 3D형태의 박스를 생성하기 위해 캔버스 생성시 WEBGL을 추가합니다.
  2. 박스 겉면에 사진을 입히기 위해 이미지를 미리 로드합니다.(preload() 메소드에서 loadImage())
  3. draw메소드에서 background는 캔버스의 배경을 칠하기 위해 처리하였습니다.
  4. specularMaterial은 질감 표현인데, 이미지를 넣으면서 큰 차이가 없게 되었습니다.
  5. ambientLight는 빛의 밝기입니다 255, 255, 255는 가장 밝으면 0, 0, 0 가장 어둡기에 박스가 검은색으로 표현됩니다.
  6. directionalLight는 특정 방향으로 물체에 쬐는 빛의 색상 및 밝기 입니다.
  7. orbitControl은 마우스 회전등의 이벤트를 제어합니다 3번째 파라미터를 0으로 처리하여 스크롤이 동작하지 않도록 막았습니다.
  8. box(넓이, 높이, 깊이)를 통해 3D 형태의 박스가 생성됩니다.
  9. texture에 먼저 로드해온 이미지를 넣어 이미지를 입힙니다.
  10. 실시간 애니메이션 효과를 위해 rotateX, Y에 값을 처리하였습니다.

 

 

 

반응형
반응형

P5.js

p5.js 무료 오픈소스 라이브러리로 다양한 이미지 처리를 통해 캔버스를 다루거나 3D입체도형, 애니메이션 처리, 기하학적인 이미지 처리 등을 좀 더 쉽게 해결할 수 있습니다.

공식 홈페이지도 한글 번역을 지원하니 보다 API 문서를 읽는데 도움이 됩니다.

p5 라이브러리를 사용하는데 중요한 메소드 2가지가 있습니다.

  • setup()
  • draw()

setup은 초기화 즉 처음에 페이지가 로드될 때, 처리할 코드를 입력합니다.

draw는 애니메이션 처리를 위한 코드를 입력합니다.

함수를 만들어서 정의만 해두면 코드가 알아서 실행됩니다.

 

시작하기

간단한 예제를 보겠습니다.

p5js.org/ko/

 

home | p5.js

안녕하세요! p5.js는 크리에이티브 코딩을 위한 자바스크립트 라이브러리로, 예술가, 디자이너, 교육자, 입문자, 그리고 모두에게 접근성 높고 포용적인 언어를 지향합니다! p5.js는 무료 오픈 소

p5js.org

먼저 공식홈페이지에서 p5.js를 다운로드 후 연결하도록 합니다.

다운로드 - p5.min.js(압축)을 받습니다.

다운로드 받은 파일을 연결 후 간단한 캔버스 생성과 도형을 그려보았습니다.

See the Pen P5.js start1 by myhappyman (@myhappyman) on CodePen.

 

또한, 마우스 이벤트를 통해 입력한 위치에 도형을 계속해서 그릴수도 있습니다.

See the Pen P5.js start2 by myhappyman (@myhappyman) on CodePen.

 

마우스 이동과 클릭에 따라 도형의 색이 달라진다!

반응형