image.png

  1. <template>
  2. <div id="app">
  3. <ul @pointermove="handlePointerMove($event)">
  4. <li v-for="item in cards" :key="item.name">
  5. <h5>{{ item.name }}</h5>
  6. <p>{{ item.desc }}</p>
  7. </li>
  8. </ul>
  9. </div>
  10. </template>
  11. <script setup lang="ts">
  12. const cards = [
  13. {
  14. name: 'Falcomon',
  15. desc: 'A bird-shaped Monster.',
  16. },
  17. {
  18. name: 'Labramon',
  19. desc: 'A Beast monster that resembles a real dog.',
  20. },
  21. {
  22. name: 'Agumon',
  23. desc: 'A monster resembling a small dinosaur.',
  24. },
  25. {
  26. name: 'Floramon',
  27. desc: 'A Vegetation monster. ',
  28. },
  29. {
  30. name: 'Kunemon',
  31. desc: 'A Larva monster with a lightning pattern on his body.',
  32. },
  33. {
  34. name: 'Lopmon',
  35. desc: 'His ecosystem is wrapped in mystery, and his composition is enough to deem him a Beast monster.',
  36. },
  37. ]
  38. const handlePointerMove = (e: any) => {
  39. const { clientX, clientY } = e
  40. document.querySelectorAll('li').forEach((item) => {
  41. const { left, top } = item.getBoundingClientRect()
  42. item.style.setProperty('--x', `${clientX - left}px`)
  43. item.style.setProperty('--y', `${clientY - top}px`)
  44. })
  45. }
  46. </script>
  47. <style lang="scss">
  48. @import url('https://fonts.googleapis.com/css?family=Press+Start+2P');
  49. #app {
  50. width: 100%;
  51. min-height: 100vh;
  52. background-color: #333;
  53. padding: 0.5em;
  54. }
  55. ul {
  56. width: 100%;
  57. display: grid;
  58. grid-column-gap: 0.5em;
  59. grid-row-gap: 0.5em;
  60. grid-template-columns: repeat(3, 1fr);
  61. li {
  62. border: 2px solid rgba(121, 67, 117, 0.3);
  63. position: relative;
  64. box-sizing: border-box;
  65. padding: 0.4em 0.8em 0.7em;
  66. font-family: 'Press Start 2P', sans-serif;
  67. background-color: rgba(90, 24, 171, 0.05);
  68. border-radius: 15px;
  69. line-height: 1.6;
  70. color: rgba(255, 255, 255, 0.36);
  71. transition: 1s color;
  72. h5 {
  73. font-size: 1.5em;
  74. margin-bottom: 0.5em;
  75. color: inherit;
  76. }
  77. p {
  78. font-size: 1.2em;
  79. color: inherit;
  80. }
  81. &:hover {
  82. color: rgba(255, 255, 255, 1);
  83. }
  84. &::after {
  85. content: '';
  86. display: block;
  87. position: absolute;
  88. top: 0;
  89. bottom: 0;
  90. left: 0;
  91. right: 0;
  92. inset: 0;
  93. border-radius: inherit;
  94. background: radial-gradient(
  95. 300px circle at var(--x) var(--y),
  96. rgba(255, 255, 255, 0.25),
  97. transparent 50%
  98. );
  99. z-index: 2;
  100. transition: all 0.5s ease;
  101. opacity: 0;
  102. }
  103. }
  104. &:hover {
  105. li::after {
  106. opacity: 1;
  107. }
  108. }
  109. }
  110. </style>

添加灯光

通过伪元素radial-gradient添加一个渐变色的浅白圈

  1. li {
  2. &::after {
  3. background: radial-gradient(
  4. 600px circle at var(--x) var(--y),
  5. rgba(255, 255, 255, 0.25),
  6. transparent 50%
  7. )
  8. }
  9. }

跟随移动

通过父元素上的handlePointerMove方法,监听pointermove事件,鼠标每次移动到父元素,就遍历每一个子元素算出当前坐标,通过setProperty改变css变量

  1. handlePointerMove(e) {
  2. const { clientX, clientY } = e;
  3. document.querySelectorAll("li").forEach(item => {
  4. const { left, top } = item.getBoundingClientRect();
  5. item.style.setProperty("--x", clientX - left + "px");
  6. item.style.setProperty("--y", clientY - top + "px");
  7. })
  8. }