Fancy cursor spring

Attaches a funny spring to your cursor on every website

  1. // ==UserScript==
  2. // @name Fancy cursor spring
  3. // @description Attaches a funny spring to your cursor on every website
  4. // @author Konf
  5. // @version 1.0.0
  6. // @namespace https://greatest.deepsurf.us/users/424058
  7. // @icon https://i.imgur.com/Gqf0kMZ.png
  8. // @resource dotImg https://i.imgur.com/Gqf0kMZ.png
  9. // @include *
  10. // @compatible Chrome
  11. // @compatible Opera
  12. // @compatible Firefox
  13. // @run-at document-idle
  14. // @grant GM_addStyle
  15. // @grant GM_getResourceURL
  16. // @noframes
  17. // ==/UserScript==
  18.  
  19. /**
  20. * Hi! Don't change (or even resave) anything here because
  21. * by doing this in Tampermonkey you will turn off updates
  22. * of the script (idk about other script managers).
  23. * This could be restored in settings but it might be hard to find,
  24. * so better to reinstall the script if you're not sure
  25. */
  26.  
  27. /**
  28. * Forked from Elastic Trail script by Philip Winston @ pwinston@yahoo.com
  29. * Original script featured on dynamicdrive.com/dynamicindex13/trailer2.htm
  30. */
  31.  
  32. /* jshint esversion: 6 */
  33.  
  34. (function() {
  35. 'use strict';
  36.  
  37. const DELTAT = .01;
  38. const SEGLEN = 10;
  39. const SPRINGK = 10;
  40. const MASS = 1;
  41. const XGRAVITY = 0;
  42. const YGRAVITY = 50;
  43. const RESISTANCE = 10;
  44. const STOPVEL = 0.1;
  45. const STOPACC = 0.1;
  46. const DOTSIZE = 18;
  47. const BOUNCE = 0.75;
  48.  
  49. const tail = {
  50. dots: [],
  51. dotUrl: null,
  52. dotsAmount: 5,
  53. container: null,
  54. };
  55.  
  56. let mX, mY, scrX, scrY;
  57.  
  58. mX = mY = scrX = scrY = 0;
  59.  
  60. // init
  61. document.addEventListener('mousemove', (ev) => {
  62. if (tail.dotsAmount) {
  63. updateMouseXY(ev);
  64.  
  65. tail.dotUrl = GM_getResourceURL('dotImg');
  66. tail.container = document.createElement('dots');
  67.  
  68. document.body.appendChild(tail.container);
  69.  
  70. // bugfix
  71. tail.dotsAmount += 1;
  72.  
  73. for (let i = 0; i < tail.dotsAmount; i++) {
  74. tail.dots[i] = new TailDot(tail.container);
  75. }
  76.  
  77. GM_addStyle(`
  78. dot.cursor-spring-dot {
  79. width: ${DOTSIZE}px;
  80. height: ${DOTSIZE}px;
  81. z-index: 2147483647;
  82. position: absolute;
  83. user-select: none;
  84. background-size: contain;
  85. background-image: url(${tail.dotUrl});
  86. }
  87. `);
  88.  
  89. // bugfix
  90. tail.dots[0].dom.display = 'none';
  91.  
  92. requestAnimationFrame(animate);
  93.  
  94. document.addEventListener('scroll', updateMouseXY);
  95. document.addEventListener('mousemove', updateMouseXY);
  96. }
  97. }, { once: true });
  98.  
  99. function Vec(X, Y) {
  100. this.X = X;
  101. this.Y = Y;
  102. }
  103.  
  104. function TailDot(container) {
  105. const dotEl = document.createElement('dot');
  106.  
  107. this.X = mX;
  108. this.Y = mY;
  109. this.dx = 0;
  110. this.dy = 0;
  111. this.dom = dotEl.style;
  112. dotEl.className = 'cursor-spring-dot';
  113.  
  114. container.appendChild(dotEl);
  115. }
  116.  
  117. // just save mouse position for animate() to use
  118. function updateMouseXY(ev) {
  119. if (ev.type === 'scroll') {
  120. mX += scrollX - scrX;
  121. mY += scrollY - scrY;
  122. scrX = scrollX;
  123. scrY = scrollY;
  124. } else {
  125. mX = ev.pageX;
  126. mY = ev.pageY;
  127. }
  128. }
  129.  
  130. // adds force in X and Y to spring for dot[i] on dot[j]
  131. function springForce(i, j, spring) {
  132. const dx = (tail.dots[i].X - tail.dots[j].X);
  133. const dy = (tail.dots[i].Y - tail.dots[j].Y);
  134. const len = Math.sqrt(dx * dx + dy * dy);
  135.  
  136. if (len > SEGLEN) {
  137. const springF = SPRINGK * (len - SEGLEN);
  138.  
  139. spring.X += (dx / len) * springF;
  140. spring.Y += (dy / len) * springF;
  141. }
  142. }
  143.  
  144. function animate() {
  145. requestAnimationFrame(animate);
  146.  
  147. tail.dots[0].X = mX;
  148. tail.dots[0].Y = mY;
  149.  
  150. for (let i = 1; i < tail.dotsAmount; i++) {
  151. const spring = new Vec(0, 0);
  152.  
  153. if (i > 0) {
  154. springForce(i - 1, i, spring);
  155. }
  156.  
  157. if (i < (tail.dotsAmount - 1)) {
  158. springForce(i + 1, i, spring);
  159. }
  160.  
  161. // air resistance/friction
  162. const resist = new Vec(-tail.dots[i].dx * RESISTANCE, -tail.dots[i].dy * RESISTANCE);
  163.  
  164. // compute new accel, including gravity
  165. const accel = new Vec((spring.X + resist.X) / MASS + XGRAVITY, (spring.Y + resist.Y) / MASS + YGRAVITY);
  166.  
  167. // compute new velocity
  168. tail.dots[i].dx += (DELTAT * accel.X);
  169. tail.dots[i].dy += (DELTAT * accel.Y);
  170.  
  171. // stop dead so it doesn't jitter when nearly still
  172. if (Math.abs(tail.dots[i].dx) < STOPVEL &&
  173. Math.abs(tail.dots[i].dy) < STOPVEL &&
  174. Math.abs(accel.X) < STOPACC &&
  175. Math.abs(accel.Y) < STOPACC) {
  176. tail.dots[i].dx = 0;
  177. tail.dots[i].dy = 0;
  178. }
  179.  
  180. // move to new position
  181. tail.dots[i].X += tail.dots[i].dx;
  182. tail.dots[i].Y += tail.dots[i].dy;
  183.  
  184. const height = document.body.clientHeight + document.body.scrollTop;
  185. const width = document.body.clientWidth + document.body.scrollLeft;
  186.  
  187. // bounce off 3 walls (leave ceiling open)
  188. if (tail.dots[i].Y >= height - DOTSIZE - 1) {
  189. if (tail.dots[i].dy > 0) {
  190. tail.dots[i].dy = BOUNCE * -tail.dots[i].dy;
  191. }
  192.  
  193. tail.dots[i].Y = height - DOTSIZE - 1;
  194. }
  195.  
  196. if (tail.dots[i].X >= width - DOTSIZE) {
  197. if (tail.dots[i].dx > 0) {
  198. tail.dots[i].dx = BOUNCE * -tail.dots[i].dx;
  199. }
  200.  
  201. tail.dots[i].X = width - DOTSIZE - 1;
  202. }
  203.  
  204. if (tail.dots[i].X < 0) {
  205. if (tail.dots[i].dx < 0) {
  206. tail.dots[i].dx = BOUNCE * -tail.dots[i].dx;
  207. }
  208.  
  209. tail.dots[i].X = 0;
  210. }
  211.  
  212. // move img to new position
  213. const newTop = Math.round(tail.dots[i].Y);
  214. const newLeft = Math.round(tail.dots[i].X);
  215.  
  216. tail.dots[i].dom.top = newTop + 'px';
  217. tail.dots[i].dom.left = newLeft + 'px';
  218. }
  219. }
  220. })();