SpongeBob SquarePants Patrick Star 티스토리 비눗방울 코드 나눔합니다.

제 블로그를 보시는 분들은 아시겠지만

마우스를 클릭하면, 뚱이가 비눗방울을 내뿜는데요.

그 코드를 지금 바로 공개합니다.

두둥

 

설정 - 스킨편집 - html 편집 들어가셔서 수정하시면 됩니다.

 

 

1. html body 최하단 삽입

- 위에가 주석 없는 버전, 아래가 주석 있는 버전입니다. 같은 코드에요.

<script>
		  (function () {
				if (window.__bubbleBound) return;
				window.__bubbleBound = true;

				function spawnBubble(x, y) {
					const el = document.createElement('div');
					el.className = 'bubble';
					const size = 25 + Math.random() * 60;
					const dx = (Math.random() - 0.5) * 90;
					const dy = -60 - Math.random() * 70;
					const dur = 700 + Math.random() * 900;

					el.style.setProperty('--x', x);
					el.style.setProperty('--y', y);
					el.style.setProperty('--size', size);
					el.style.setProperty('--dx', dx + 'px');
					el.style.setProperty('--dy', dy + 'px');
					el.style.setProperty('--dur', dur);

					document.body.appendChild(el);
					el.addEventListener('animationend', () => el.remove());
				}

				function onPointer(e) {
					const x = e.clientX ?? (e.touches && e.touches[0]?.clientX);
					const y = e.clientY ?? (e.touches && e.touches[0]?.clientY);
					if (typeof x !== 'number' || typeof y !== 'number') return;

					const count = 6 + Math.floor(Math.random() * 5);
					for (let i = 0; i < count; i++) {
						setTimeout(() => spawnBubble(
							x + (Math.random()-0.5)*70,
							y + (Math.random()-0.5)*70
						), i * 70);
					}
				}

				document.addEventListener('pointerdown', onPointer, { passive: true, capture: true });
				document.addEventListener('touchstart', e => {
					const t = e.touches && e.touches[0];
					if (!t) return;
					onPointer({ clientX: t.clientX, clientY: t.clientY });
				}, { passive: true, capture: true });
			})();
			</script>
<script>
  (function () {                             // 즉시 실행 함수(IIFE): 전역 오염 없이 한 번만 초기화
    if (window.__bubbleBound) return;        // 이미 바인딩된 적 있으면 중복 초기화 방지
    window.__bubbleBound = true;             // 한 번 바인딩했음을 표시하는 플래그

    function spawnBubble(x, y) {             // 비눗방울 하나 생성하는 함수
      const el = document.createElement('div');     // div 요소 생성
      el.className = 'bubble';                       // CSS에서 스타일링할 클래스 지정

      const size = 25 + Math.random() * 60;         // 버블 크기(25~85px) 랜덤
      const dx = (Math.random() - 0.5) * 90;        // 가로로 살짝 흩어지는 이동량(-45~+45px)
      const dy = -60 - Math.random() * 70;          // 위쪽으로 떠오를 이동량(-60~-130px)
      const dur = 1000 + Math.random() * 900;       // 애니메이션 지속시간(700~1600ms)

      el.style.setProperty('--x', x);               // 시작 X 좌표를 CSS 변수로 전달
      el.style.setProperty('--y', y);               // 시작 Y 좌표를 CSS 변수로 전달
      el.style.setProperty('--size', size);         // 크기를 CSS 변수로 전달
      el.style.setProperty('--dx', dx + 'px');      // 가로 이동량을 CSS 변수로 전달
      el.style.setProperty('--dy', dy + 'px');      // 세로 이동량을 CSS 변수로 전달
      el.style.setProperty('--dur', dur);           // 애니메이션 시간(ms)을 CSS 변수로 전달

      document.body.appendChild(el);                // 문서에 버블 추가 → 애니메이션 시작
      el.addEventListener('animationend', () => el.remove()); // 애니메이션 끝나면 DOM에서 제거(누적 방지)
    }

    function onPointer(e) {                         // 포인터(마우스/펜/터치) 입력 핸들러
      const x = e.clientX ?? (e.touches && e.touches[0]?.clientX); // 입력 이벤트에서 X 좌표 추출(터치 호환)
      const y = e.clientY ?? (e.touches && e.touches[0]?.clientY); // 입력 이벤트에서 Y 좌표 추출
      if (typeof x !== 'number' || typeof y !== 'number') return;  // 좌표 없으면 종료

      const count = 6 + Math.floor(Math.random() * 5); // 한 번에 생성할 버블 수(6~10개)
      for (let i = 0; i < count; i++) {                // count만큼 반복 생성
        setTimeout(() => spawnBubble(                  // 약간의 시간차를 두고 생성(폭죽처럼 퍼짐)
          x + (Math.random() - 0.5) * 70,             // 클릭 지점 기준 가로 랜덤 오프셋(-35~+35px)
          y + (Math.random() - 0.5) * 70              // 클릭 지점 기준 세로 랜덤 오프셋(-35~+35px)
        ), i * 70);                                    // i에 비례한 지연(0ms, 70ms, 140ms, …)
      }
    }

    document.addEventListener('pointerdown', onPointer, { passive: true, capture: true });
    // 포인터 다운(클릭/펜/터치 시작) 시 onPointer 호출
    // passive: 스크롤 성능 최적화(기본 동작 차단 안 함), capture: 캡처 단계에서 먼저 받음(상위에서 가로채여도 동작)

    document.addEventListener('touchstart', e => {     // 일부 브라우저/상황용 터치 보조 핸들러
      const t = e.touches && e.touches[0];             // 첫 번째 터치 포인트
      if (!t) return;                                  // 없으면 종료
      onPointer({ clientX: t.clientX, clientY: t.clientY }); // 터치 좌표를 포인터 형태로 래핑해 전달
    }, { passive: true, capture: true });

  })();                                                // 즉시 실행하여 리스너를 한 번만 세팅
</script>

 

 

 

2. css 최하단에 삽입

- 이건 왜인지 모르게 주석이 계속 깨지는데요.

마찬가지로 위에는 주석 최소화 버전이고 아래는 주석 버전입니다.

/* 비눗방울 효과 시작 !!!!!!!!! */
:root {
  --bubble-glow: rgba(255,255,255,0.8);
}

:root[data-theme="light"] {
  --dark-bg-color: #ffffff;           /* 라이트 배경 */
  --dark-font-color: #111111;         /* 라이트 텍스트 */
  --dark-bg-second-color: #f4f6f8;    /* 서브 배경 */
  --sidebar-hr-color: rgba(0,0,0,.12);
  /* --highlight-color: #ff4081; */
}


/* 비눗방울 본체 */
.bubble {
  position: fixed;
  left: calc(var(--x) * 1px);
  top: calc(var(--y) * 1px);
  width: calc(var(--size, 30) * 1px);
  height: calc(var(--size, 30) * 1px);
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%) scale(0);
  opacity: 0;
  z-index: 2147483647;
  animation: bubble-rise calc(var(--dur, 1400) * 0.5ms) ease-in-out forwards;
  will-change: transform, opacity, filter;
  mix-blend-mode: screen;
  background:
    radial-gradient(35% 35% at 30% 28%,
    rgba(255,255,255,.95) 0%,
    rgba(255,255,255,.35) 35%,
    rgba(255,255,255,0) 60%),
  conic-gradient(from 0deg,
    rgba(255,180,180,.30),
    rgba(255,240,200,.28),
    rgba(190,255,220,.28),
    rgba(200,220,255,.30),
    rgba(255,180,255,.28),
    rgba(255,180,180,.30));
  background-blend-mode: screen, lighten;
  box-shadow:
    0 0 25px 6px rgba(255,255,255,0.25),
    inset 0 0 25px rgba(255,255,255,0.25);
  filter: saturate(1.2) brightness(1.05);
}

/* 하이라이트 */
.bubble::before {
  content: "";
  position: absolute;
  left: 18%;
  top: 12%;
  width: 40%;
  height: 40%;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.7), rgba(255,255,255,0) 70%);
  opacity: 0.4;
}

.bubble::after {
  content: "";
  position: absolute;
  left: 60%;
  top: 58%;
  width: 25%;
  height: 25%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.5), rgba(255,255,255,0) 80%);
  opacity: 0.4;
}


/* 색 변화 + 위로 이동 */
@keyframes bubble-rise {
  0% {
    transform: translate(-20%, -20%) scale(0.3);
    opacity: 0;
    filter: hue-rotate(0deg);
  }
  25% {
    transform: translate(-20%, -20%) scale(1.1);
    opacity: 1;
    filter: hue-rotate(60deg);
  }
  60% {
    transform: translate(calc(-20% + var(--dx, 0px)), calc(-20% + var(--dy, -50px))) scale(0.95);
    opacity: 0.95;
    filter: hue-rotate(180deg) brightness(1.35);
  }
  100% {
    transform: translate(calc(-20% + var(--dx, 0px)), calc(-20% + var(--dy, -130px))) scale(0.8);
    opacity: 0;
    filter: hue-rotate(360deg);
  }
}
/* 비눗방울 효과 시작 */
:root {
  --bubble-glow: rgba(255,255,255,0.8); /* 전체에서 재사용할 기본 발광색(흰빛) 변수 */
}

:root[data-theme="light"] {
  --dark-bg-color: #ffffff;           /* 라이트 모드 배경색 */
  --dark-font-color: #111111;         /* 라이트 모드 글자색 */
  --dark-bg-second-color: #f4f6f8;    /* 서브 배경 */
  --sidebar-hr-color: rgba(0,0,0,.12);/* 구분선 색 */
  /* --highlight-color: #ff4081;  강조색(현재 미사용) */
}

/* 비눗방울 본체 */
.bubble {
  position: fixed; /* 화면 스크롤과 상관없이 고정 위치 */
  left: calc(var(--x) * 1px); /* JS로 전달된 X좌표 */
  top: calc(var(--y) * 1px);  /* JS로 전달된 Y좌표 */
  width: calc(var(--size, 30) * 1px);  /* 랜덤 크기 (기본 30px) */
  height: calc(var(--size, 30) * 1px);
  border-radius: 50%; /* 원형으로 만듦 */
  pointer-events: none; /* 클릭 이벤트를 막지 않음 */
  transform: translate(-50%, -50%) scale(0); /* 중앙 기준으로 작게 시작 */
  opacity: 0; /* 처음엔 투명 */
  z-index: 2147483647; /* 최상단에 표시 */
  animation: bubble-rise calc(var(--dur, 1400) * 0.5ms) ease-in-out forwards;
  /* bubble-rise 애니메이션을 실행. 지속시간은 JS에서 지정한 --dur * 0.5ms */
  will-change: transform, opacity, filter; /* 애니메이션 성능 향상 힌트 */
  mix-blend-mode: screen; /* 배경 위에 밝은 색이 자연스럽게 섞이도록 함 */

  /* 비눗방울의 색감과 질감 (2개의 레이어가 섞임) */
  background:
    radial-gradient(35% 35% at 30% 28%, /* 중심부 반사광 */
      rgba(255,255,255,.95) 0%,         /* 중심 흰색 강한 반사 */
      rgba(255,255,255,.35) 35%,        /* 중간 영역: 연한 빛 */
      rgba(255,255,255,0) 60%),         /* 외곽으로 갈수록 투명 */
    conic-gradient(from 0deg,           /* 원형으로 퍼지는 오로라 색상 */
      rgba(255,180,180,.30),            /* 연한 핑크 */
      rgba(255,240,200,.28),            /* 크림빛 노랑 */
      rgba(190,255,220,.28),            /* 민트 */
      rgba(200,220,255,.30),            /* 하늘색 */
      rgba(255,180,255,.28),            /* 보라핑크 */
      rgba(255,180,180,.30));           /* 다시 핑크로 돌아옴 */
  background-blend-mode: screen, lighten; /* 위 두 레이어를 밝게 섞음 */
  box-shadow:
    0 0 25px 6px rgba(255,255,255,0.25),   /* 외곽에 은은한 발광 */
    inset 0 0 25px rgba(255,255,255,0.25); /* 내부에서 은은히 비치는 빛 */
  filter: saturate(1.2) brightness(1.05);  /* 약간 더 밝고 선명하게 */
}

/* 상단 왼쪽의 반사광 */
.bubble::before {
  content: "";
  position: absolute;
  left: 18%;  /* 버블 내 상대 위치 */
  top: 12%;
  width: 40%;
  height: 40%;
  border-radius: 50%;
  background: radial-gradient(circle at 30% 30%, /* 작게 흰 점 반사 */
    rgba(255,255,255,0.7),
    rgba(255,255,255,0) 70%);
  opacity: 0.4; /* 투명하게 빛나게 */
}

/* 하단 오른쪽의 은은한 반사광 */
.bubble::after {
  content: "";
  position: absolute;
  left: 60%;  /* 버블 내부 위치 */
  top: 58%;
  width: 25%;
  height: 25%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, /* 옅은 하이라이트 */
    rgba(255,255,255,0.5),
    rgba(255,255,255,0) 80%);
  opacity: 0.4;
}

/* 버블이 위로 떠오르며 색이 회전하는 애니메이션 */
@keyframes bubble-rise {
  0% {
    transform: translate(-20%, -20%) scale(0.3); /* 작게 시작 (왼쪽 위 기준) */
    opacity: 0;                /* 완전히 투명 */
    filter: hue-rotate(0deg);  /* 색상 회전 없음 (기본색) */
  }
  25% {
    transform: translate(-20%, -20%) scale(1.1); /* 커지며 나타남 */
    opacity: 1;                /* 완전히 보이게 */
    filter: hue-rotate(60deg); /* 색이 조금 바뀜 */
  }
  60% {
    transform: translate(calc(-20% + var(--dx, 0px)),
                         calc(-20% + var(--dy, -50px))) scale(0.95);
    /* 랜덤 이동 + 위로 조금 올라감 */
    opacity: 0.95;
    filter: hue-rotate(180deg) brightness(1.35); /* 한층 밝아짐 */
  }
  100% {
    transform: translate(calc(-20% + var(--dx, 0px)),
                         calc(-20% + var(--dy, -130px))) scale(0.8);
    /* 더 높이 올라가며 작아짐 */
    opacity: 0;                 /* 사라짐 */
    filter: hue-rotate(360deg); /* 색이 한 바퀴 돌아 원래로 */
  }
}

 

  • :root 부분은 테마 변수.
  • .bubble은 비눗방울의 모양·질감.
  • ::before, ::after는 반사광 효과.
  • @keyframes bubble-rise는 위로 떠오르며 색이 도는 애니메이션.