관련지식
javascript, jquery

윈도우 탐색기에서 마우스 drag로 여러 파일을 선택하는 것은 대부분 자주 이용할 것입니다. 그러나 웹 화면에선 그러한 인터페이스를 찾아보기가 어렵습니다. 필요성이 없어서 일수도 있고 어떻게해야 할지 몰라서 일수도 있을것 같습니다.

이번엔 마우스로 드래그 영역내 특정 DOM을 선택하는 기능을 만들어보겠습니다.
이전에 작성했던 ‘웹 화면 부분 캡쳐 만들기’, ‘DOM Inspector 만들기’ 와 유사한 부분이 많으므로 앞의 글을 먼저 읽어보는 것도 도움이 되실 겁니다.

완성되는 결과물은 아래와 같습니다. 마우스를 클릭하여 드래그하면 선택영역 안에 정확히 들어가는 DOM만 선택(파란색테두리 생김) 됩니다. 완성된 소스는 맨 아래에 있고 본문에선 소스의 중요한 부분만 언급 하겠습니다.

테스트용 Element 생성

이 기능이 잘 동작하는지 다양한 케이스를 만들기 위해 테스트용 DOM을 랜덤하게 생성할 것입니다. 테스트할 화면이 있다면 필요없는 단계입니다. 이 소스는 10개의 <div> 객체를 포함하는 3줄 짜리 <div>를 만들게 됩니다.

  1. function init() { //테스트를 위한 div 생성
  2. for(var i = 0; i < 3; i++) {
  3. var $row = $('<div class="row"></div>');
  4. for(var j = 0; j < 10; j++) {
  5. var div = $("<div></div>");
  6. var r = Math.random() * 1000 % 200;
  7. div.css("background-color", 'rgb(' + [r, r, r].join(',') + ')');
  8. div.css("width", r);
  9. div.css("height", r);
  10. $row.append(div);
  11. }
  12. $container.append($row);
  13. }
  14. }

마우스 선택영역 그리기

마우스를 ‘클릭’ 한 상태로 ‘이동’하면 테두리만 있는 투명한 박스를 그립니다. ‘클릭’ 떼면 투명한 박스 영역내 모든 DOM을 선택할 것입니다. 이전에 ‘웹 화면 부분 캡쳐 만들기’ 에서는 border-width 속성을 이용해서 그러한 효과를 만들었지만 이번에는 border 속성을 이용하여 만들겠습니다.

  1. $(document).on("mousedown", function(e) {
  2. startX = e.clientX;
  3. startY = e.clientY;
  4. });

마우스를 클릭한 위치가 투명한 박스 영역의 기준점이 될 것입니다. 클릭한 좌표를 기준으로 마우스가 우측하단으로 움직일때와 좌측상단으로 움직일때를 고려해야 합니다.

  1. $(document).on('mousemove', function(e) {
  2. var x = e.clientX;
  3. var y = e.clientY;
  4. //마우스 이동에 따라 선택 영역을 리사이징 한다
  5. width = Math.max(x - startX, startX - x);
  6. left = Math.min(startX, x);
  7. $focus.css('left', left);
  8. $focus.css("width", width);
  9. height = Math.max(y - startY, startY - y);
  10. top = Math.min(startY, y);
  11. $focus.css('top', top);
  12. $focus.css('height', height);
  13. });

영역내 객체 선택

마우스 이동후 클릭을 떼면 그려진 선택 영역 안에 들어가는 객체만 선택해야 합니다. 아래 이미지를 보겠습니다.

빨간색 화살표가 가리키는 3개의 객체는 선택 영역 안에 완전히 포함되지 않고 걸쳐있습니다. 걸쳐진 이미지는 선택하지 않을 것입니다. 따라서 선택 영역의 x1,y1 좌표보다 크고 x2,y2 좌표 이내에 위치한 객체만 선택 시킬 것입니다.

  1. var target = '#container div'; //필요한대로 변경해야 한다.
  2. $(document).on('mouseup', function(e) {
  3. //범위 내 객체를 선택한다.
  4. rangeSelect(target, left, top, left + width, top + height, function(include) {
  5. if(include)
  6. $(this).addClass('highlight');
  7. else
  8. $(this).removeClass('highlight');
  9. });
  10. });
  11. function rangeSelect(selector, x1, y1, x2, y2, cb) {
  12. $(selector).each(function() {
  13. var $this = $(this);
  14. var offset = $this.offset();
  15. var x = offset.left;
  16. var y = offset.top;
  17. var w = $this.width();
  18. var h = $this.height();
  19. //범위 안인지 체크
  20. cb.call(this, x >= x1 && y >= y1 && x + w <= x2 && y + h <= y2);
  21. });
  22. }

좌표안에 있는 객체만 찾을수 있는 함수가 있다면 좋겠지만 그런 함수는 없기 때문에 selector를 기준으로 모든 객체를 가져와 하나하나 위치를 비교해야 합니다. 따라서 selector 가 가리키는 객체가 많을 수록 성능 이슈가 발생할 수 있습니다. 이 코드에선 mousemove 이벤트에서는 rangeSelect()를 호출하지 않기 때문에 이슈가 적은 편입니다.

정리

만약 객체의 상하 관계에 따른 중복 선택 증상을 개선하려면 위의 코드에서 로직을 조금 추가해야 합니다. 그 내용이 들어가면 기본 코드가 더 복잡해질수 있어서 추가하진 않았지만 혹시 필요하신 분이 계시면 댓글 달아주세요.

최종 소스

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  7. <style>
  8. #container div {float:left}
  9. .focus {position:fixed; border:1px solid red; background-color:rgb(128, 0, 0, 0.3);}
  10. .highlight {border:1px solid #000099;}
  11. </style>
  12. </head>
  13. <body>
  14. <div id="container"></div>
  15. <div class="focus"></div>
  16. <script>
  17. var $container = $("#container");
  18. init();
  19. function init() { //테스트를 위한 div 생성
  20. for(var i = 0; i < 3; i++) {
  21. var $row = $('<div class="row"></div>');
  22. for(var j = 0; j < 10; j++) {
  23. var div = $("<div></div>");
  24. var r = Math.random() * 1000 % 200;
  25. div.css("background-color", 'rgb(' + [r, r, r].join(',') + ')');
  26. div.css("width", r);
  27. div.css("height", r);
  28. $row.append(div);
  29. }
  30. $container.append($row);
  31. }
  32. }
  33. (function() {
  34. var target = '#container div'; //셀렉트로 묶을 객체
  35. var mode = false;
  36. var startX = 0;
  37. var startY = 0;
  38. var left, top, width, height;
  39. var $focus = $(".focus");
  40. $(document).on("mousedown", function(e) {
  41. mode = true;
  42. startX = e.clientX;
  43. startY = e.clientY;
  44. width = height = 0;
  45. $focus.show();
  46. rangeSelect(target, 0, 0, 0, 0, function() { //기존에 선택된 객체를 선택취소한다.
  47. $(this).removeClass('highlight');
  48. });
  49. }).on('mouseup', function(e) {
  50. mode = false;
  51. $focus.hide();
  52. $focus.css("width", 0);
  53. $focus.css('height', 0);
  54. //범위 내 객체를 선택한다.
  55. rangeSelect(target, left, top, left + width, top + height, function(include) {
  56. if(include)
  57. $(this).addClass('highlight');
  58. else
  59. $(this).removeClass('highlight');
  60. });
  61. }).on('mousemove', function(e) {
  62. if(!mode) {
  63. return;
  64. }
  65. var x = e.clientX;
  66. var y = e.clientY;
  67. //마우스 이동에 따라 선택 영역을 리사이징 한다
  68. width = Math.max(x - startX, startX - x);
  69. left = Math.min(startX, x);
  70. $focus.css('left', left);
  71. $focus.css("width", width);
  72. height = Math.max(y - startY, startY - y);
  73. top = Math.min(startY, y);
  74. $focus.css('top', top);
  75. $focus.css('height', height);
  76. });
  77. function rangeSelect(selector, x1, y1, x2, y2, cb) {
  78. $(selector).each(function() {
  79. var $this = $(this);
  80. var offset = $this.offset();
  81. var x = offset.left;
  82. var y = offset.top;
  83. var w = $this.width();
  84. var h = $this.height();
  85. //범위 안인지 체크
  86. cb.call(this, x >= x1 && y >= y1 && x + w <= x2 && y + h <= y2);
  87. });
  88. }
  89. })();
  90. </script>
  91. </body>
  92. </html>