J-Web

SmartCursor로 마우스 커서 커스텀하기

테스트 페이지 바로가기

CSS의 cursor 속성을 이용하는 경우 구현의 한계가 있다.
요소를 생성하여 더 멋지게 커스텀 해본 예제이다.
JS로 체크해서 PC웹에서만 적용이 가능하다.


CSS

/* Smart Cursor Style */
.smart_cursor	{display:none;position:fixed;left:0;top:0;z-index:10000;pointer-events:none;}
.smart_cursor.enter	{display:block;}
.smart_cursor:before	{content:'';display:block;width:20px;height:20px;border-radius:50%;background-color:#d33;transform:translate(-50%, -50%);}	/* 디폴트 커서 */
.smart_cursor [data-smart-cursor]	{opacity:0;visibility:hidden;width:100px;height:100px;position:absolute;left:-50px;top:-50px;background-repeat:no-repeat;background-size:100% 100%;transform:scale(0.2);transition:all 0.2s;}
.smart_cursor [data-smart-cursor].on	{opacity:1;visibility:visible;transform:scale(1);}
.smart_cursor [data-smart-cursor='anchor']	{background-image:url('cursor_1.png');}
.smart_cursor [data-smart-cursor='drag']	{background-image:url('cursor_2.png');}

JS

const SmartCursor = {
	cursor: {},
	enter: function(){
		SmartCursor.cursor.parent.classList.add('enter');
	},
	leave: function(){
		SmartCursor.cursor.parent.classList.remove('enter');
		SmartCursor.cursor.parent.removeAttribute('style');
	},
	move: function(mouse){
		if(!SmartCursor.cursor.parent.classList.contains('enter')){
			SmartCursor.enter();
		}
		SmartCursor.cursor.parent.style.transform = 'translate(' + mouse.clientX + 'px, ' + mouse.clientY + 'px)';
	},
	toggle: function(cursorType){
		SmartCursor.cursor[cursorType].classList.toggle('on');
	},
	init: function(){
		// 커서 요소 생성
		SmartCursor.cursor.parent = document.createElement('div');
		SmartCursor.cursor.parent.classList.add('smart_cursor');
		document.body.append(SmartCursor.cursor.parent);

		const cursors = document.querySelectorAll('[data-cursor]');

		if(cursors.length){
			const tempCursors = [];	// 페이지 내 존재하는 cursor를 담을 임시 배열

			cursors.forEach(function(cursor, idx){
				tempCursors.push(cursor.dataset.cursor);

				cursor.addEventListener('mouseenter', function(){
					SmartCursor.toggle(cursor.dataset.cursor);
				});
				cursor.addEventListener('mouseleave', function(){
					SmartCursor.toggle(cursor.dataset.cursor);
				});
			});

			// 임시 배열에서 중복되는 항목 제거
			const realCursors = tempCursors.filter(function(item, idx){
				return tempCursors.indexOf(item) === idx;
			});

			// 커서 요소 삽입
			realCursors.forEach(function(ele, idx){
				SmartCursor.cursor[ele] = document.createElement('div');
				SmartCursor.cursor[ele].dataset.smartCursor = ele;
				SmartCursor.cursor.parent.append(SmartCursor.cursor[ele]);
			});
		}

		document.documentElement.addEventListener('mouseenter', SmartCursor.enter);
		document.documentElement.addEventListener('mouseleave', SmartCursor.leave);
		document.documentElement.addEventListener('mousemove', SmartCursor.move);

		// iframe
		const iframeObj = document.querySelectorAll('iframe');

		if(iframeObj.length){
			const frameEnter = function(){
				document.querySelector('.smart_cursor').style.display = 'none';
			};
			const frameLeave = function(){
				document.querySelector('.smart_cursor').style.display = '';
			};

			iframeObj.forEach(function(ele){
				ele.addEventListener('mouseenter', frameEnter);
				ele.addEventListener('mouseleave', frameLeave);
				ele.addEventListener('mousemove', frameEnter);
			});
		}

		// smart cursor를 적용하지 않는 요소 중 클릭 가능한 요소들
		const clickable = document.querySelectorAll('a:not([data-cursor]), button:not([data-cursor]), label:not([data-cursor])');

		if(clickable.length){
			const clickableEnter = function(){
				document.querySelector('.smart_cursor').classList.add('clickable');
			};
			const clickableLeave = function(){
				document.querySelector('.smart_cursor').classList.remove('clickable');
			};

			clickable.forEach(function(ele){
				ele.addEventListener('mouseenter', clickableEnter);
				ele.addEventListener('mouseleave', clickableLeave);
			});
		}
	}
};
SmartCursor.init();