document.body('mousedown', (e)=>console.log(e.button, e.buttons));
document.body('mousemove', (e)=>console.log(e.button, e.buttons));
document.body('mouseup', (e)=>console.log(e.button, e.buttons));

마우스 이벤트에서 이벤트를 일으킨 마우스의 버튼들을 알고자한다면, 위와 같이 마우스 이벤트의 buttonbuttons 두 가지 속성을 사용할 수 있다(which도 있지만 표준이 아니므로 사용하지 않는 것이 좋을 것 같다.). MDN에서는 두 속성에 대해서 아래와 같이 설명하고 있다.

MouseEvent.button

The button number that was pressed when the mouse event was fired.

0: Main button pressed, usually the left button or the un-initialized state

1: Auxiliary button pressed, usually the wheel button or the middle button (if present)

2: Secondary button pressed, usually the right button

3: Fourth button, typically the Browser Back button

4: Fifth button, typically the Browser Forward button

MouseEvent.button 속성은 하나 또는 여러 개의 단추를 누르거나 놓음으로써 발생하는 마우스 이벤트의 올바른 값만을 보장한다. mousedownmouseup에 대해서는 정상적으로 동작하지만 mousemove에서는 누르고 있는 마우스 버튼과 관계없이 0을 반환한다.

MouseEvent.buttons

The buttons being pressed when the mouse event was fired. Each button that can be pressed is represented by a given number (see below). If more than one button is pressed, the value of the buttons is combined to produce a new number.

0: No button or un-initialized

1: Left button

2: Right button

4: Wheel button or middle button

8: 4th button (typically the “Browser Back” button)

16: 5th button (typically the “Browser Forward” button)

MouseEvent.buttons 속성은 모든 종류의 마우스 이벤트 중에 눌려진 버튼들의 상태를 나타낸다. buttons라는 이름에 걸맞게 복수의 마우스 버튼들을 누르고 있으면, 상단의 값들의 합으로 구분할 수 있다. 예를 들어 마우스 왼쪽 버튼(1)과 마우스 오른쪽 버튼(2)를 함께 누르면 3을 값으로 가지고 있음을 확인할 수 있다. 버튼 값들이 2진수의 각 자리값에 해당하므로 눌러진 버튼을 구분하여 사용할 수 있다.

Reference

배열에서 중복 값을 제거할 때 종종 Set이나 Object를 써서 처리했었는데, 얼마전 재밌는 코드를 봤다.

const arr = ["1", "1", "2", "3", "3", "1"];
const unique = arr.filter((value, idx, arr) => arr.indexOf(value) === idx);
// ["1", "2", "3"]

해당 코드를 상단과 같은 코드였는데, 처음 (value, idx, arr) => arr.indexOf(value) === idx 이 부분을 봤을때는 무슨 용도인가 한참 생각을 했다. callback의 세번째 인자로 넘오는 원본 배열인 arr을 사용해 별도의 처리 없이 중복 값을 제거하는 부분이 신선했다.

기본으로 구현된 커서가 아닌 커스텀된 커서가 필요하여 알아보다보니 Changing the Cursor with CSS for Better User Experience (or Fun) 이라는 글을 보게되어 몇가지 방법을 시도해보았다.

제일 먼저 Element를 JavaScript로 이동하여 구현하기 손쉬어 포인터처럼 구현하는 것을 시도해봤는데 해당 Element와 바탕이 되는 Element의 이벤트 처리가 껄끄러워 제외하였다.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<svg viewBox="0 0 12 12"
     version="1.1"
     xmlns:svg="http://www.w3.org/2000/svg"
     xmlns="http://www.w3.org/2000/svg"
     width="12"
     height="12">
  <circle cx="6" cy="6" r="4" stroke="#000" stroke-opacity="0.75" stroke-width="2" fill="#fff" fill-opacity="0.5"/>
</svg>
.element-png {
  text-align: center;
  color: #ffffff;
  padding: 30px;
  width: 300px;
  height: 300px;
  background: red;
  border-radius: 400px;
  cursor: url(./cursor.png) 6 6, auto;  
}

.element-svg {
  text-align: center;
  color: #ffffff;
  padding: 30px;
  width: 300px;
  height: 300px;
  background: red;
  border-radius: 400px;
  cursor: url(./cursor.svg) 6 6, auto;  
}

.element-svg-base64 {
  text-align: center;
  color: #ffffff;
  padding: 30px;
  width: 300px;
  height: 300px;
  background: red;
  border-radius: 400px;
  cursor: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB2aWV3Qm94PSIwIDAgMTIgMTIiICAgICB2ZXJzaW9uPSIxLjEiICAgICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiAgICAgd2lkdGg9IjEyIiAgICAgaGVpZ2h0PSIxMiI+ICA8Y2lyY2xlIGN4PSI2IiBjeT0iNiIgcj0iNCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC43NSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSIjZmZmIiBmaWxsLW9wYWNpdHk9IjAuNSIvPjwvc3ZnPg==) 6 6, auto;  
}

그외 직접 이미지가 필요한 커서의 경우 상단과 같은 SVG를 만들어서 svg, base64, png를 만들어서 사용하였다.

세 방법 모두 잘 작동하여서 현 프로젝트에서 관리에 용이한 쪽으로 선택하여 사용하려고 한다. png을 사용하는 방법의 경우 디자이너가 관리를 하게 되어, 이미지 변경에 대해 직접 신경쓰지 않아도 될 것 같다. 하지만 이 커서가 스타일이 변경되어야 되는 경우에, 단지 이 부분만 수정하면 될 것이 아니고 해당 부분은 개발자가 수정해야 하기 때문에, svg를 사용하는 쪽이 커서의 수정이 용이해서 선호한다.

function copyImageData(ctx, src) {
  var dst = ctx.createImageData(src.width, src.height);
  dst.data.set(src.data);
  return dst;
}

Canvas에서 getImageData로 가져온 ImageData를 복사하고 싶다면 상단과 같이 복사할 수 있다. 새로운 Canvas에 ImageDataputImageData로 넣어서 사용하기도 했었지만 ImageData만을 복사하여 조작하고자 하여 사용하였다.

// window popup open
window.open('test.html');

// popup
window.opener.document.querySelector('#test');

최근 새로운 팝업을 열고 해당 팝업에서 부모창을 컨트롤해야하는 일이 있어서 위와 같이 window.opener를 통해 부모의 윈도우를 접근하였다.

SecurityError: Blocked a frame with origin "http://example.com" from accessing a cross-origin frame

그런데 동일 도메인 상에서 팝업을 열었는데도 opener.document를 접근하니 동일 출처 정책을 위반하였다는 에러가 발생하였다. 예를 들자면 test.example.com/index.html에서 팝업을 열었는데, test.example.com/test.html에서 부모의 document를 접근하지 못하는 상태였다.

// parent
document.domain = 'example.com';

// child
document.domain = 'example.com';

알고보니 상단과 같이 스크립트를 통해 현재 도메인이나 현재 도메인의 상위 도메인으로 변경으로 페이지 출처를 변경할 수 있다는 사실을 알았다. 부모창내 코드 어디에선가 document.domain를 변경한 부분이 있었고, 자식창에서 똑같이 페이지 출처를 설정해줌으로써 해결할 수 있었다.