Browser cat

A small cat that follows your cursor

  1. // ==UserScript==
  2. // @name Browser cat
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @license MIT
  6. // @description A small cat that follows your cursor
  7. // @author Boofdev
  8. // @match *://*/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=githubusercontent.com
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function oneko() {
  14. const nekoEl = document.createElement("div");
  15. let nekoPosX = 32;
  16. let nekoPosY = 32;
  17. let mousePosX = 0;
  18. let mousePosY = 0;
  19. let frameCount = 0;
  20. let idleTime = 0;
  21. let idleAnimation = null;
  22. let idleAnimationFrame = 0;
  23. const nekoSpeed = 10;
  24. const spriteSets = {
  25. idle: [[-3, -3]],
  26. alert: [[-7, -3]],
  27. scratch: [
  28. [-5, 0],
  29. [-6, 0],
  30. [-7, 0],
  31. ],
  32. tired: [[-3, -2]],
  33. sleeping: [
  34. [-2, 0],
  35. [-2, -1],
  36. ],
  37. N: [
  38. [-1, -2],
  39. [-1, -3],
  40. ],
  41. NE: [
  42. [0, -2],
  43. [0, -3],
  44. ],
  45. E: [
  46. [-3, 0],
  47. [-3, -1],
  48. ],
  49. SE: [
  50. [-5, -1],
  51. [-5, -2],
  52. ],
  53. S: [
  54. [-6, -3],
  55. [-7, -2],
  56. ],
  57. SW: [
  58. [-5, -3],
  59. [-6, -1],
  60. ],
  61. W: [
  62. [-4, -2],
  63. [-4, -3],
  64. ],
  65. NW: [
  66. [-1, 0],
  67. [-1, -1],
  68. ],
  69. };
  70. function create() {
  71. nekoEl.id = "oneko";
  72. nekoEl.style.width = "32px";
  73. nekoEl.style.height = "32px";
  74. nekoEl.style.position = "fixed";
  75. nekoEl.style.pointerEvents = "none";
  76. nekoEl.style.backgroundImage = "url('https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif')";
  77. nekoEl.style.imageRendering = "pixelated";
  78. nekoEl.style.left = "16px";
  79. nekoEl.style.top = "16px";
  80.  
  81. document.body.appendChild(nekoEl);
  82.  
  83. document.onmousemove = (event) => {
  84. mousePosX = event.clientX;
  85. mousePosY = event.clientY;
  86. };
  87.  
  88. window.onekoInterval = setInterval(frame, 100);
  89. }
  90.  
  91. function setSprite(name, frame) {
  92. const sprite = spriteSets[name][frame % spriteSets[name].length];
  93. nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${
  94. sprite[1] * 32
  95. }px`;
  96. }
  97.  
  98. function resetIdleAnimation() {
  99. idleAnimation = null;
  100. idleAnimationFrame = 0;
  101. }
  102.  
  103. function idle() {
  104. idleTime += 1;
  105.  
  106. // every ~ 20 seconds
  107. if (
  108. idleTime > 10 &&
  109. Math.floor(Math.random() * 200) == 0 &&
  110. idleAnimation == null
  111. ) {
  112. idleAnimation = ["sleeping", "scratch"][
  113. Math.floor(Math.random() * 2)
  114. ];
  115. }
  116.  
  117. switch (idleAnimation) {
  118. case "sleeping":
  119. if (idleAnimationFrame < 8) {
  120. setSprite("tired", 0);
  121. break;
  122. }
  123. setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
  124. if (idleAnimationFrame > 192) {
  125. resetIdleAnimation();
  126. }
  127. break;
  128. case "scratch":
  129. setSprite("scratch", idleAnimationFrame);
  130. if (idleAnimationFrame > 9) {
  131. resetIdleAnimation();
  132. }
  133. break;
  134. default:
  135. setSprite("idle", 0);
  136. return;
  137. }
  138. idleAnimationFrame += 1;
  139. }
  140.  
  141. function frame() {
  142. frameCount += 1;
  143. const diffX = nekoPosX - mousePosX;
  144. const diffY = nekoPosY - mousePosY;
  145. const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
  146.  
  147. if (distance < nekoSpeed || distance < 48) {
  148. idle();
  149. return;
  150. }
  151.  
  152. idleAnimation = null;
  153. idleAnimationFrame = 0;
  154.  
  155. if (idleTime > 1) {
  156. setSprite("alert", 0);
  157. // count down after being alerted before moving
  158. idleTime = Math.min(idleTime, 7);
  159. idleTime -= 1;
  160. return;
  161. }
  162.  
  163. direction = diffY / distance > 0.5 ? "N" : "";
  164. direction += diffY / distance < -0.5 ? "S" : "";
  165. direction += diffX / distance > 0.5 ? "W" : "";
  166. direction += diffX / distance < -0.5 ? "E" : "";
  167. setSprite(direction, frameCount);
  168.  
  169. nekoPosX -= (diffX / distance) * nekoSpeed;
  170. nekoPosY -= (diffY / distance) * nekoSpeed;
  171.  
  172. nekoEl.style.left = `${nekoPosX - 16}px`;
  173. nekoEl.style.top = `${nekoPosY - 16}px`;
  174. }
  175.  
  176. create();
  177. })();