先上效果图:
再上代码:
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title><style>html, body, canvas{-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;}html, body{width: 100%;height: 100%;overflow: hidden;margin: 0;display: flex;align-items: center;justify-content: center;background: #191919;}.asset-img{display: none;}</style></head><body><canvas></canvas><imgclass="asset-img"id="light-img"src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAABGdBTUEA
ALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACnVBMVEUAAAD40ED40D740Dv40En40Dr40FH40Df40D/40D340
Dn40Dz40Dj40AD40Ff40Db40DT40EH40EL40ED40ED40Dv40D/4zz/4zz74zz/4zz/40D74zz/4zz/4zz/40UH40kD40UL40kH4zz/4zz340D34zz740kH41U
T410f410j410j410f40D/40D/4zz/40ED40EH400T410f42Er4103410/411D41UT40kH40D/4z0D4zz/41UX410j42E7411L42Ff42Vv42Vz42En41kb40UH
4zz/40Dz40ED41UT42Ej4107411b42l743WT432r4323432r410/42En4zz340Dn400H410f42mD432v44nf45IP45Yn45IT44nj432r411b400L40D/4zz/4
1UT42Er411P432v44oD46Zv47q/48Lj46Zr432n40UH410f42Ff44nf48r/499T4997499T48r744nf43WP40D741EH42Vv4+O34+PP4+O347q745IL432j40
UD432345Yj48LX49934+PP4+Pj4993477T45Ib432z411D400D40D/40UH432n45IP47a7432j42Vv410/400H40Dr4zz340UD410b410346Zj48r343WP42F
f410f40D/41kT42Er42l744nv46Zf42l3411L410r41kX40ED410f42E7411b421/45IL45Ib432n421740UH40D340Dz4zz741EP410j410743WT432f432v
43mj43GT42Ej41kT4zz740ED41EX411P42Ff42Fv411f410j41kX400P41034107411D42E340D740UH410j41kb41ET400H40D/40ED4z0D4zz74zz340D//
//+WdN8lAAAA3nRSTlMAAAAAAAAAAAAAAAAAAAAAAAAAAgMCBAcJDAkDCA0SFRsbGxMDBQscJSwwMS0SDAUFFyExQExUVyIYDQULJzlOZHeDhDonFwoDASE5V
XGNpbO4tFM5BwEcMJa3zdjb2Mu2cBwDESM+Y7bU4ebo4bUUK3bL6vDy7+rKogIZgPb49ebXsBu02+jx9/jw6Nu0VRsBGbDX5q9/UhkCCRUqS+HqoHMqESI9i9
Thi2E9IhEvTG+V1tq0kxsDAQchN1Ggr7SvoDghChUjYXJ/cjgnIUlSVEkFGi8qIhsFFAcICAaJi/ApAAAAAWJLR0Te6W7imwAAAAd0SU1FB+YMHBEUINvSacM
AAAIKSURBVCjPY2AgBBgZmZiFRYQZgQBVnEVUTFxCUlJSSlxMlAVJjplVWkZWTl5BUUleWVZGhZkZYZKqmpy6hqaWtpaOhrqunj7MREZGA1lDI2MTUzNzM1MT
YwtLK2uIFCOjiI2hrZ29g6OTs5Ojg72Lq5u7Bxs7SMZTXNfL28fXzz8gMMjfzzc4xEI3NIwVrEUyPMLeNzIqOiY2Lj4hMtE+Ikk2mYkRKCOekpqW7peRmZWdk
52bmeeXnqYhJ84BkpHKLzAt9C/KKi4pLSvPragsNNXMl/JkZGCqUqvWMqsJiMkuqa2rL2tobKox06qWTOZg4EiWbNY2d25pbWvv6Ozo6u7pde7T6pf04GTgmG
AzEahn0uSGsvqO+rIpjVOnTdeaoTaTmYGLfdbsOXMLKyvmzS/rKps/r2jBwrmL5KWAfmViEl+8ZGn6srzlK6Z0T1mxPG/lqtVr1oLdxp0sqbRu/YaNeUWbNm8
q2rJ1w/p12yS38wDDh3PHTrldu/dsWLZ33/4DB5dt2HPosO7OMFBwM/KKHjl6bPf64yemOZ88dXz96TPyR6T5+EFByi6ganP07LrV585fmH5x6bqzs21UmZhg
0XDJXe7ykjnrtK5cvXZdzv0GMycs6jiZpXdKLs532+Z2M0VSXAQ5KTAKeorcuu1uc+TO3e07hNDSCCMjNyczKz8nN8FkxgAAgH2hXtQzzA4AAAAldEVYdGRhd
GU6Y3JlYXRlADIwMjItMTItMjhUMTc6MjA6MzIrMDA6MDA1MNVoAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTEyLTI4VDE3OjIwOjMyKzAwOjAwRG1t1AAAAA
BJRU5ErkJggg=="alt="base64"></body><script>classMouse{constructor(canvas){this.pos =newVector(-1000,-1000)this.radius =40
canvas.onmousemove=e=>this.pos.setXY(e.clientX, e.clientY)
canvas.ontouchmove=e=>this.pos.setXY(e.touches[0].clientX, e.touches[0].clientY)
canvas.ontouchcancel=()=>this.pos.setXY(-1000,-1000)
canvas.ontouchend=()=>this.pos.setXY(-1000,-1000)}}classDot{constructor(x, y){this.pos =newVector(x, y)this.oldPos =newVector(x, y)this.friction =0.97this.gravity =newVector(0,0.6)this.mass =1this.pinned =falsethis.lightImg = document.querySelector('#light-img')this.lightSize =15}update(mouse){if(this.pinned)returnlet vel = Vector.sub(this.pos,this.oldPos)this.oldPos.setXY(this.pos.x,this.pos.y)
vel.mult(this.friction)
vel.add(this.gravity)let{x: dx,y: dy }= Vector.sub(mouse.pos,this.pos)const dist = Math.sqrt(dx * dx + dy * dy)const direction =newVector(dx / dist, dy / dist)const force = Math.max((mouse.radius - dist)/ mouse.radius,0)if(force >0.6)this.pos.setXY(mouse.pos.x, mouse.pos.y)else{this.pos.add(vel)this.pos.add(direction.mult(force))}}drawLight(ctx){
ctx.drawImage(this.lightImg,this.pos.x -this.lightSize /2,this.pos.y -this.lightSize /2,this.lightSize,this.lightSize
)}draw(ctx){
ctx.fillStyle ='#aaa'
ctx.fillRect(this.pos.x -this.mass,this.pos.y -this.mass,this.mass *2,this.mass *2)}}classStick{constructor(p1, p2){this.startPoint = p1
this.endPoint = p2
this.length =this.startPoint.pos.dist(this.endPoint.pos)this.tension =0.3}update(){const dx =this.endPoint.pos.x -this.startPoint.pos.x
const dy =this.endPoint.pos.y -this.startPoint.pos.y
const dist = Math.sqrt(dx * dx + dy * dy)const diff =(dist -this.length)/ dist
const offsetX = diff * dx *this.tension
const offsetY = diff * dy *this.tension
const m =this.startPoint.mass +this.endPoint.mass
const m1 =this.endPoint.mass / m
const m2 =this.startPoint.mass / m
if(!this.startPoint.pinned){this.startPoint.pos.x += offsetX * m1
this.startPoint.pos.y += offsetY * m1
}if(!this.endPoint.pinned){this.endPoint.pos.x -= offsetX * m2
this.endPoint.pos.y -= offsetY * m2
}}draw(ctx){
ctx.beginPath()
ctx.strokeStyle ='#999'
ctx.moveTo(this.startPoint.pos.x,this.startPoint.pos.y)
ctx.lineTo(this.endPoint.pos.x,this.endPoint.pos.y)
ctx.stroke()
ctx.closePath()}}classRope{constructor(config){this.x = config.x
this.y = config.y
this.segments = config.segments ||10this.gap = config.gap ||15this.color = config.color ||'gray'this.dots =[]this.sticks =[]this.iterations =10this.create()}pin(index){this.dots[index].pinned =true}create(){for(let i =0; i <this.segments; i++){this.dots.push(newDot(this.x,this.y + i *this.gap))}for(let i =0; i <this.segments -1; i++){this.sticks.push(newStick(this.dots[i],this.dots[i +1]))}}update(mouse){this.dots.forEach(dot=>{
dot.update(mouse)})for(let i =0; i <this.iterations; i++){this.sticks.forEach(stick=>{
stick.update()})}}draw(ctx){this.dots.forEach(dot=>{
dot.draw(ctx)})this.sticks.forEach(stick=>{
stick.draw(ctx)})this.dots[this.dots.length -1].drawLight(ctx)}}classApp{static width = innerWidth
static height = innerHeight
static dpr = devicePixelRatio >1?2:1static interval =1000/60constructor(){this.canvas = document.querySelector('canvas')this.ctx =this.canvas.getContext('2d')this.mouse =newMouse(this.canvas)this.resize()
window.addEventListener('resize',this.resize.bind(this))this.createRopes()}createRopes(){this.ropes =[]constTOTAL= App.width *0.06for(let i =0; i <TOTAL+1; i++){const x =randomNumBetween(App.width *0.3, App.width *0.7)const y =0const gap =randomNumBetween(App.height *0.05, App.height *0.08)const segments =10const rope =newRope({ x, y, gap, segments })
rope.pin(0)this.ropes.push(rope)}}resize(){
App.width = innerWidth
App.height = innerHeight
this.canvas.style.width ='100%'this.canvas.style.height ='100%'this.canvas.width = App.width * App.dpr
this.canvas.height = App.height * App.dpr
this.ctx.scale(App.dpr, App.dpr)this.createRopes()}render(){let now, delta
let then = Date.now()constframe=()=>{requestAnimationFrame(frame)
now = Date.now()
delta = now - then
if(delta < App.interval)return
then = now -(delta % App.interval)this.ctx.clearRect(0,0, App.width, App.height)// draw herethis.ropes.forEach(rope=>{
rope.update(this.mouse)
rope.draw(this.ctx)})}requestAnimationFrame(frame)}}functionrandomNumBetween(min, max){return Math.random()*(max - min)+ min
}
window.addEventListener('load',()=>{const app =newApp()
app.render()})classVector{constructor(x, y){this.x = x ||0this.y = y ||0}staticadd(v1, v2){returnnewVector(v1.x + v2.x, v1.y + v2.y)}staticsub(v1, v2){returnnewVector(v1.x - v2.x, v1.y - v2.y)}add(x, y){if(arguments.length ===1){this.x += x.x
this.y += x.y
}elseif(arguments.length ===2){this.x += x
this.y += y
}returnthis}sub(x, y){if(arguments.length ===1){this.x -= x.x
this.y -= x.y
}elseif(arguments.length ===2){this.x -= x
this.y -= y
}returnthis}mult(v){if(typeof v ==='number'){this.x *= v
this.y *= v
}else{this.x *= v.x
this.y *= v.y
}returnthis}setXY(x, y){this.x = x
this.y = y
returnthis}dist(v){const dx =this.x - v.x
const dy =this.y - v.y
return Math.sqrt(dx * dx + dy * dy)}}</script></html>
代码直接粘贴到html页面就能使用,顺滑的不可言说
本文转载自: https://blog.csdn.net/qq_35241329/article/details/130427447
版权归原作者 timi先生 所有, 如有侵权,请联系我们删除。
版权归原作者 timi先生 所有, 如有侵权,请联系我们删除。