参考文章:Hexo页脚养鱼效果
-
引入必须的
jquery
文件 -
把上述文件保存在
\themes\butterfly\source\js
并命名为jquery.min.js
和fish.js
-
在
\themes\butterfly\source\js
路径下新建fish.js
文件,代码内容如下:1fish(); 2function fish() { 3 return ( 4 $("footer").append( 5 '<div class="fish_container" id="jsi-flying-fish-container"></div>' 6 ), 7 $(".fish_container").css({ 8 "z-index": -1, 9 width: "100%", 10 height: "160px", 11 margin: 0, 12 padding: 0, 13 }), 14 $("#footer-wrap").css({ 15 position: "absolute", 16 "text-align": "center", 17 top: 0, 18 right: 0, 19 left: 0, 20 bottom: 0, 21 }), 22 this 23 ); 24} 25var RENDERER = { 26 POINT_INTERVAL : 5, 27 FISH_COUNT : 3, 28 MAX_INTERVAL_COUNT : 50, 29 INIT_HEIGHT_RATE : 0.5, 30 THRESHOLD : 50, 31 32 init : function(){ 33 this.setParameters(); 34 this.reconstructMethods(); 35 this.setup(); 36 this.bindEvent(); 37 this.render(); 38 }, 39 setParameters : function(){ 40 this.$window = $(window); 41 this.$container = $('#jsi-flying-fish-container'); 42 this.$canvas = $('<canvas />'); 43 this.context = this.$canvas.appendTo(this.$container).get(0).getContext('2d'); 44 this.points = []; 45 this.fishes = []; 46 this.watchIds = []; 47 }, 48 createSurfacePoints : function(){ 49 var count = Math.round(this.width / this.POINT_INTERVAL); 50 this.pointInterval = this.width / (count - 1); 51 this.points.push(new SURFACE_POINT(this, 0)); 52 53 for(var i = 1; i < count; i++){ 54 var point = new SURFACE_POINT(this, i * this.pointInterval), 55 previous = this.points[i - 1]; 56 57 point.setPreviousPoint(previous); 58 previous.setNextPoint(point); 59 this.points.push(point); 60 } 61 }, 62 reconstructMethods : function(){ 63 this.watchWindowSize = this.watchWindowSize.bind(this); 64 this.jdugeToStopResize = this.jdugeToStopResize.bind(this); 65 this.startEpicenter = this.startEpicenter.bind(this); 66 this.moveEpicenter = this.moveEpicenter.bind(this); 67 this.reverseVertical = this.reverseVertical.bind(this); 68 this.render = this.render.bind(this); 69 }, 70 setup : function(){ 71 this.points.length = 0; 72 this.fishes.length = 0; 73 this.watchIds.length = 0; 74 this.intervalCount = this.MAX_INTERVAL_COUNT; 75 this.width = this.$container.width(); 76 this.height = this.$container.height(); 77 this.fishCount = this.FISH_COUNT * this.width / 500 * this.height / 500; 78 this.$canvas.attr({width : this.width, height : this.height}); 79 this.reverse = false; 80 81 this.fishes.push(new FISH(this)); 82 this.createSurfacePoints(); 83 }, 84 watchWindowSize : function(){ 85 this.clearTimer(); 86 this.tmpWidth = this.$window.width(); 87 this.tmpHeight = this.$window.height(); 88 this.watchIds.push(setTimeout(this.jdugeToStopResize, this.WATCH_INTERVAL)); 89 }, 90 clearTimer : function(){ 91 while(this.watchIds.length > 0){ 92 clearTimeout(this.watchIds.pop()); 93 } 94 }, 95 jdugeToStopResize : function(){ 96 var width = this.$window.width(), 97 height = this.$window.height(), 98 stopped = (width == this.tmpWidth && height == this.tmpHeight); 99 100 this.tmpWidth = width; 101 this.tmpHeight = height; 102 103 if(stopped){ 104 this.setup(); 105 } 106 }, 107 bindEvent : function(){ 108 this.$window.on('resize', this.watchWindowSize); 109 this.$container.on('mouseenter', this.startEpicenter); 110 this.$container.on('mousemove', this.moveEpicenter); 111 this.$container.on('click', this.reverseVertical); 112 }, 113 getAxis : function(event){ 114 var offset = this.$container.offset(); 115 116 return { 117 x : event.clientX - offset.left + this.$window.scrollLeft(), 118 y : event.clientY - offset.top + this.$window.scrollTop() 119 }; 120 }, 121 startEpicenter : function(event){ 122 this.axis = this.getAxis(event); 123 }, 124 moveEpicenter : function(event){ 125 var axis = this.getAxis(event); 126 127 if(!this.axis){ 128 this.axis = axis; 129 } 130 this.generateEpicenter(axis.x, axis.y, axis.y - this.axis.y); 131 this.axis = axis; 132 }, 133 generateEpicenter : function(x, y, velocity){ 134 if(y < this.height / 2 - this.THRESHOLD || y > this.height / 2 + this.THRESHOLD){ 135 return; 136 } 137 var index = Math.round(x / this.pointInterval); 138 139 if(index < 0 || index >= this.points.length){ 140 return; 141 } 142 this.points[index].interfere(y, velocity); 143 }, 144 reverseVertical : function(){ 145 this.reverse = !this.reverse; 146 147 for(var i = 0, count = this.fishes.length; i < count; i++){ 148 this.fishes[i].reverseVertical(); 149 } 150 }, 151 controlStatus : function(){ 152 for(var i = 0, count = this.points.length; i < count; i++){ 153 this.points[i].updateSelf(); 154 } 155 for(var i = 0, count = this.points.length; i < count; i++){ 156 this.points[i].updateNeighbors(); 157 } 158 if(this.fishes.length < this.fishCount){ 159 if(--this.intervalCount == 0){ 160 this.intervalCount = this.MAX_INTERVAL_COUNT; 161 this.fishes.push(new FISH(this)); 162 } 163 } 164 }, 165 render : function(){ 166 requestAnimationFrame(this.render); 167 this.controlStatus(); 168 this.context.clearRect(0, 0, this.width, this.height); 169 this.context.fillStyle = 'hsl(0, 0%, 95%)'; 170 171 for(var i = 0, count = this.fishes.length; i < count; i++){ 172 this.fishes[i].render(this.context); 173 } 174 this.context.save(); 175 this.context.globalCompositeOperation = 'xor'; 176 this.context.beginPath(); 177 this.context.moveTo(0, this.reverse ? 0 : this.height); 178 179 for(var i = 0, count = this.points.length; i < count; i++){ 180 this.points[i].render(this.context); 181 } 182 this.context.lineTo(this.width, this.reverse ? 0 : this.height); 183 this.context.closePath(); 184 this.context.fill(); 185 this.context.restore(); 186 } 187}; 188var SURFACE_POINT = function(renderer, x){ 189 this.renderer = renderer; 190 this.x = x; 191 this.init(); 192}; 193SURFACE_POINT.prototype = { 194 SPRING_CONSTANT : 0.03, 195 SPRING_FRICTION : 0.9, 196 WAVE_SPREAD : 0.3, 197 ACCELARATION_RATE : 0.01, 198 199 init : function(){ 200 this.initHeight = this.renderer.height * this.renderer.INIT_HEIGHT_RATE; 201 this.height = this.initHeight; 202 this.fy = 0; 203 this.force = {previous : 0, next : 0}; 204 }, 205 setPreviousPoint : function(previous){ 206 this.previous = previous; 207 }, 208 setNextPoint : function(next){ 209 this.next = next; 210 }, 211 interfere : function(y, velocity){ 212 this.fy = this.renderer.height * this.ACCELARATION_RATE * ((this.renderer.height - this.height - y) >= 0 ? -1 : 1) * Math.abs(velocity); 213 }, 214 updateSelf : function(){ 215 this.fy += this.SPRING_CONSTANT * (this.initHeight - this.height); 216 this.fy *= this.SPRING_FRICTION; 217 this.height += this.fy; 218 }, 219 updateNeighbors : function(){ 220 if(this.previous){ 221 this.force.previous = this.WAVE_SPREAD * (this.height - this.previous.height); 222 } 223 if(this.next){ 224 this.force.next = this.WAVE_SPREAD * (this.height - this.next.height); 225 } 226 }, 227 render : function(context){ 228 if(this.previous){ 229 this.previous.height += this.force.previous; 230 this.previous.fy += this.force.previous; 231 } 232 if(this.next){ 233 this.next.height += this.force.next; 234 this.next.fy += this.force.next; 235 } 236 context.lineTo(this.x, this.renderer.height - this.height); 237 } 238}; 239var FISH = function(renderer){ 240 this.renderer = renderer; 241 this.init(); 242}; 243FISH.prototype = { 244 GRAVITY : 0.4, 245 246 init : function(){ 247 this.direction = Math.random() < 0.5; 248 this.x = this.direction ? (this.renderer.width + this.renderer.THRESHOLD) : -this.renderer.THRESHOLD; 249 this.previousY = this.y; 250 this.vx = this.getRandomValue(4, 10) * (this.direction ? -1 : 1); 251 252 if(this.renderer.reverse){ 253 this.y = this.getRandomValue(this.renderer.height * 1 / 10, this.renderer.height * 4 / 10); 254 this.vy = this.getRandomValue(2, 5); 255 this.ay = this.getRandomValue(0.05, 0.2); 256 }else{ 257 this.y = this.getRandomValue(this.renderer.height * 6 / 10, this.renderer.height * 9 / 10); 258 this.vy = this.getRandomValue(-5, -2); 259 this.ay = this.getRandomValue(-0.2, -0.05); 260 } 261 this.isOut = false; 262 this.theta = 0; 263 this.phi = 0; 264 }, 265 getRandomValue : function(min, max){ 266 return min + (max - min) * Math.random(); 267 }, 268 reverseVertical : function(){ 269 this.isOut = !this.isOut; 270 this.ay *= -1; 271 }, 272 controlStatus : function(context){ 273 this.previousY = this.y; 274 this.x += this.vx; 275 this.y += this.vy; 276 this.vy += this.ay; 277 278 if(this.renderer.reverse){ 279 if(this.y > this.renderer.height * this.renderer.INIT_HEIGHT_RATE){ 280 this.vy -= this.GRAVITY; 281 this.isOut = true; 282 }else{ 283 if(this.isOut){ 284 this.ay = this.getRandomValue(0.05, 0.2); 285 } 286 this.isOut = false; 287 } 288 }else{ 289 if(this.y < this.renderer.height * this.renderer.INIT_HEIGHT_RATE){ 290 this.vy += this.GRAVITY; 291 this.isOut = true; 292 }else{ 293 if(this.isOut){ 294 this.ay = this.getRandomValue(-0.2, -0.05); 295 } 296 this.isOut = false; 297 } 298 } 299 if(!this.isOut){ 300 this.theta += Math.PI / 20; 301 this.theta %= Math.PI * 2; 302 this.phi += Math.PI / 30; 303 this.phi %= Math.PI * 2; 304 } 305 this.renderer.generateEpicenter(this.x + (this.direction ? -1 : 1) * this.renderer.THRESHOLD, this.y, this.y - this.previousY); 306 307 if(this.vx > 0 && this.x > this.renderer.width + this.renderer.THRESHOLD || this.vx < 0 && this.x < -this.renderer.THRESHOLD){ 308 this.init(); 309 } 310 }, 311 render : function(context){ 312 context.save(); 313 context.translate(this.x, this.y); 314 context.rotate(Math.PI + Math.atan2(this.vy, this.vx)); 315 context.scale(1, this.direction ? 1 : -1); 316 context.beginPath(); 317 context.moveTo(-30, 0); 318 context.bezierCurveTo(-20, 15, 15, 10, 40, 0); 319 context.bezierCurveTo(15, -10, -20, -15, -30, 0); 320 context.fill(); 321 322 context.save(); 323 context.translate(40, 0); 324 context.scale(0.9 + 0.2 * Math.sin(this.theta), 1); 325 context.beginPath(); 326 context.moveTo(0, 0); 327 context.quadraticCurveTo(5, 10, 20, 8); 328 context.quadraticCurveTo(12, 5, 10, 0); 329 context.quadraticCurveTo(12, -5, 20, -8); 330 context.quadraticCurveTo(5, -10, 0, 0); 331 context.fill(); 332 context.restore(); 333 334 context.save(); 335 context.translate(-3, 0); 336 context.rotate((Math.PI / 3 + Math.PI / 10 * Math.sin(this.phi)) * (this.renderer.reverse ? -1 : 1)); 337 338 context.beginPath(); 339 340 if(this.renderer.reverse){ 341 context.moveTo(5, 0); 342 context.bezierCurveTo(10, 10, 10, 30, 0, 40); 343 context.bezierCurveTo(-12, 25, -8, 10, 0, 0); 344 }else{ 345 context.moveTo(-5, 0); 346 context.bezierCurveTo(-10, -10, -10, -30, 0, -40); 347 context.bezierCurveTo(12, -25, 8, -10, 0, 0); 348 } 349 context.closePath(); 350 context.fill(); 351 context.restore(); 352 context.restore(); 353 this.controlStatus(context); 354 } 355}; 356$(function(){ 357 RENDERER.init(); 358});
-
在主题配置文件
_config.butterfly.yml
的inject
,在bottom
直接引入两个js
文件 -
在
\themes\butterfly\source\css
路径下创建一个footer.css
文件1.fish_container{ 2 z-index: -1; 3 width: "100%"; 4 height: "160px"; 5 margin: 0; 6 padding: 0; 7} 8#footer-wrap{ 9 position: absolute; 10 text-align: center; 11 padding: 20px 20px; 12 top: 0; 13 right: 0; 14 left: 0; 15 bottom: 0; 16}
-
设置透明度,在
\themes\butterfly\source\css
路径下创建一个fish_transparent.css
文件1/* 页脚半透明 */ 2#footer { 3 background: rgba(255, 255, 255, 0); 4 color: #000; 5 border-top-right-radius: 20px; 6 border-top-left-radius: 20px; 7 backdrop-filter: saturate(100%) blur(5px) 8} 9 10#footer::before { 11 background: rgba(255,255,255,0) 12} 13 14#footer #footer-wrap { 15 color: var(--font-color); 16} 17 18#footer #footer-wrap a { 19 color: var(--font-color); 20}
1/* 页脚透明 */ 2#footer { 3 background: transparent !important; 4}
-
在主题配置文件
_config.butterfly.yml
的inject
,在head
直接引入css
文件1- <link rel="stylesheet" href="/css/footer.css"> 2- <link rel="stylesheet" href="/css/fish_transparent.css">
-
注意
HTML
中<script>
引入jQuery
文件的顺序,要把JQuery
库的引用放到第一个<script>
引用前面,这样顺序执行后面的js
文件才能识别。 -
修改小鱼的颜色,颜色值可以参考 HSL