[js] 모바일브라우저용 콘솔을 만들자.

개요

개발자 입장에서 모바일 브라우저는 거의 대부분 소스보기와 디버거를 제공하지 않아 매우 불편합니다. 결국 pc와 연결해야하거나 전용 브라우저를 깔아야하지만, 모바일브라우저도 나름대로의 특성이 있기 때문에 사파리에서 생긴 문제는 사파리에서 봐야하고 크롬에서 생긴 문제는 크롬에서 봐야합니다.
해서 귀찮지만 간단한 콘솔을 만들어서 확인하도록 해보죠.

아이디어

postion:fixed를 이용하여 바닥에 div영역을 생성한 뒤

  1. 여기에 직접 기록한 로그와
  2. HTML의 innerHTML을 보여주는 정도를

목표로 삼아보죠. 대충 완성되고 나면 다음과 같은 화면이 될 것입니다.
a
화면 하단에 달라붙어있는 콘솔창에 콘솔탭에서는 log를 호출해서 찍은 값이 보일테고, element탭에는 현재의 html.innerHTML을 출력해주면 되겠죠.

기초구현

우선 콘솔이 될 div영역을 설정해야 할 것입니다. 위의 예상 그림을 다시 영역으로 나눠서 확인해보죠.

b

콘솔 안에 탭영역과 뷰영역이 있고 그 안에 각각 재료들이 들어가 있는 구성입니다.
의존성을 최소화하기 위해 전부 인라인스타일로 정의해보죠(나중에는 style태그에 몰아주면 됩니다)

var html = [
//전체 콘솔창 - fixed로 화면하단에 붙인다.
'<div id="bsConsole" style="position:fixed;z-index:999999;background:#eee;bottom:0;left:0;right:0;height:200px">',
  //상단 탭 두 개가 들어갈 박스
  '<div id="bsConsoleTab" style="background:#ccc;height:20px">',
    //두개의 탭
    '<div id="bsConsoleTabConsole" style="font-size:11px;margin:2px 5px;padding:0 5px;float:left;border:1px solid #666">', 
       'Console',
    '</div>',
    '<div id="bsConsoleTabElement" style="font-size:11px;margin:2px 5px;padding:0 5px;float:left;border:1px solid #666">', 
       'Element',
    '</div>',
  '</div>',
  //탭에 따른 내용이 나올 공간
  '<div id="bsConsoleView" style="font-size:10px;overflow-y:scroll;height:180px">',
    //콘솔용 뷰
    '<div id="bsConsoleViewConsole"></div>',
    //엘리먼트용 뷰
    '<div id="bsConsoleViewElement" style="display:none"></div>',
  '</div>',
'</div>'
]

이제 html은 갖게 되었으니

  1. 문서에 없다면 삽입하고,
  2. 이벤트 리스너는 bsConsole에 걸어 e.target으로 위임하게 됩니다.
var temp = document.createElement('div');
var init = function(){
  
  if(!document.getElementById('bsConsole')){

    //div를 하나 만들어서 innerHTML을 통해 콘솔뷰를 생성한뒤
    temp.innerHTML = html.join('');

    //body에 넣어줌
    document.body.appendChild(temp.childNodes[0]);

    //이벤트를 걸어줌
    document.getElementById('bsConsole').onclick = function(e){
      switch(e.target.id){ //각 타겟의 아이디별로 분기
      ...
      }
    };
};

대충 이 정도 뼈대입니다.

각 버튼의 기능정의

위에서 정의한 뼈대에 이벤트 리스너만 상세하게 구현해보죠.

  1. 탭이 들어있는 상단 바는 콘솔자체를 접었다 폈다 하는 기능을 제공하겠죠.
  2. 우선 각 탭은 공통적으로 관련된 view로 교체하는 역할을 수행할 것입니다.
  3. Element탭은 현재의 html내용을 가져와서 view에 표시해야합니다.

머..고작 이 정도입니다.

document.getElementById('bsConsole').onclick = function(e){
    switch(e.target.id){
    //상단바
    case'bsConsoleTab'://접었다 폈다.
      if(e.target.style.height == '200px'){//펴져있으니 접자
        e.target.style.height == '20px';
      }else{//펴자
        e.target.style.height == '200px';
      }
      break;
   //Element탭버튼
   case'bsConsoleTabElement'://뷰를 전환하고 html을 뿌려줌
      document.getElementById('bsConsoleViewConsole').style.display = 'none';
      document.getElementById('bsConsoleViewElement').style.display = 'block';
      document.getElementById('bsConsoleTabElement').innerHTML = '<pre>' + 
        ('<html>\n' + document.getElementsByTagName('html')[0].innerHTML + '\n</html>').replace(/[<]/g, '&lt;') + 
        '</pre>';
      break;
   //콘솔탭버튼
   case'bsConsoleTabConsole'://뷰만 전환해줌
      document.getElementById('bsConsoleViewConsole').style.display = 'block';
      document.getElementById('bsConsoleViewElement').style.display = 'none';
   }
};

단순해서 설명할게 없습니다. 단지 콘솔은 기본적인 기능으로 의존성없이 구현하는게 중요하기 때문에 라이브러리를 사용하지 않고 전부 날코드로 구현해 코드가 깁니다.

log함수의 정의

결국 이 가상 콘솔시스템에서 로그함수란 bsConsoleViewConsole에 차곡차곡 로그로 보낸 내용을 써가는 과정이라 할 수 있습니다.
시스템의 console.log에서는 객체를 출력해주기도 하고 인자를 여러 개 보내면 컴마로 나눠서 다 출력해줍니다.

객체 출력은 그 부분만 해도 하나의 포스트 분량이므로 이번엔 생략하기로 하고 여러 개의 인자를 받아주는 정도만 이 포스트에서 구현하도록 하죠.

var temp = document.createElement('div');
var log = function(){
  var a = arguments, i = 0, j = a.length, item. v;

  init(); //콘솔창을 초기화해줌

  //이번 로그를 출력할 줄의 시작을 정의한다.
  item = ['<div style="clear:both">'];

  while(i < j){
    //출력할 값
    v = a[i++]; 
    //객체면 문자열화한다.
    if(v && typeof v == 'object') v = JSON.stringify(v); 

    //html조각에 추가해준다.
    item.push(
      //아이템은 컴마로 연결될 것이므로 float:left로 연결
      '<div style="font-size:11px;border:1px solid #000;padding:5px;margin:5px;float:left>',
          a[i++],
      '</div>',
    );
  }
  item[item.length] = '</div>'; //마지막으로 줄을 마감하여 정리함.

  //DOM으로 변환하여 콘솔뷰에 추가해준다.
  temp.innerHTML = temp.join('');
  document.getElementById(bsConsoleViewConsole).appendChild(temp.childNodes[0]);
}

사용하기

위의 코드를 전체적으로 모아 하나의 가상 콘솔객체를 만든 뒤 log메소드를 노출하는 방식으로 사용하면 아래와 같이 정리될 것입니다.

var mConsole = (function(){
  var html = [
  '<style>',
  '#bsConsole{position:fixed;z-index:999999;background:#eee;bottom:0;left:0;right:0;height:200px}',
  '#bsConsoleTab{background:#ccc;height:20px}',
  '#bsConsoleTabConsole,#bsConsoleTabElement{font-size:11px;margin:2px 5px;padding:0 5px;float:left;border:1px solid #666}',
  '#bsConsoleView{font-size:10px;overflow-y:scroll;height:180px}',
  '#bsConsoleViewElement{word-break:break-all;word-wrap:break-word}',
  '.bsConsoleItem{font-size:11px;border:1px solid #000;padding:5px;margin:5px;float:left}',
  '</style>',
  '<div id="bsConsole">',
    '<div id="bsConsoleTab">',
      '<div id="bsConsoleTabConsole">Console</div><div id="bsConsoleTabElement">Element</div>',
    '</div>',
    '<div id="bsConsoleView">',
      '<div id="bsConsoleViewConsole"></div><div id="bsConsoleViewElement" style="display:none"></div>',
    '</div>',
  '</div>'],
  temp = document.createElement('div'),
  init = function(){
    if(document.getElementById('bsConsole')) return;
      temp.innerHTML = html.join('');
      document.body.appendChild(temp.childNodes[0]);
	  document.body.appendChild(temp.childNodes[0]);
      document.getElementById('bsConsole').onclick = function(e){
      switch(e.target.id){
      case'bsConsoleTab':
        e.target.style.height = e.target.style.height == '200px' ? '20px' : '200px';
        break;
      case'bsConsoleTabElement':
        document.getElementById('bsConsoleViewConsole').style.display = 'none';
        document.getElementById('bsConsoleViewElement').style.display = 'block';
        document.getElementById('bsConsoleViewElement').innerHTML = '<pre>' + 
          ('<html>\n' + document.getElementsByTagName('html')[0].innerHTML + '\n</html>').replace(/[<]/g, '&lt;') + 
          '</pre>';
        break;
      case'bsConsoleTabConsole':
        document.getElementById('bsConsoleViewConsole').style.display = 'block';
        document.getElementById('bsConsoleViewElement').style.display = 'none';
      }
    }
  };
  return {
    log:function(){
      var a = arguments, i = 0, j = a.length, item, v;
      init();
	  item = ['<div style="clear:both">'];
      while(i < j){
        v = a[i++]; 
        if(v && typeof v == 'object') v = JSON.stringify(v); 
        item.push('<div class="bsConsoleItem">' + v + '</div>');
      }
	  item.push('</div>');
      temp.innerHTML = item.join('');
      document.getElementById('bsConsoleViewConsole').appendChild(temp.childNodes[0]);
    }
  };
})();

사용하는 형태는 console과 매우 흡사해지겠죠.

mConsole.log('test', obj, 123);

다음과 같은 화면을 볼 수 있습니다.
c

Element탭을 누르면 현재의 html상태를 볼 수 있을 것입니다. 마지막 정리된 코드에는 word-break:break-all;word-wrap:break-word 를 추가해줬으므로 길게 연결된 문자열로 분리하여 보여줍니다(만약 이 뷰를 트리뷰로 보고 싶다면 따로 구현해야겠죠. 그건 또 다른 문제이므로 다른 포스트에서..)
d

결론

고작 60줄도 안되는 의존성없는 작은 코드 조각으로도 모바일에서의 개발환경을 매우 편리하게 바꿀 수 있습니다. 이미 운영 중인 사이트에는 적용할 수 없지만 사이트 개발시에 확인용으로는 상당히 쓸만하죠.
log함수를 개선하여 여러가지 기능을 붙여가다보면 언젠가는 파이어버그가 될테고 언젠가는 크롬콘솔창과 비슷해질 것입니다(..만 필요한 범위는 개인차가 있으니까요 ^^)

%d 블로거가 이것을 좋아합니다: