feat: svg学习笔记
@ -1,898 +0,0 @@
|
||||
---
|
||||
nav:
|
||||
title: 前端
|
||||
path: /fea
|
||||
group:
|
||||
title: 💊 canvas
|
||||
order: 6
|
||||
---
|
||||
|
||||
## 动画案例
|
||||
|
||||
### 粒子背景
|
||||
|
||||
<code src="./demos/demo3/index.jsx" />
|
||||
|
||||
### 粒子图片
|
||||
|
||||
<code src="./demos/demo2/index.jsx" />
|
||||
|
||||
### 贪吃蛇
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
async function init() {
|
||||
var snake = [41, 40], //snake队列表示蛇身,初始节点存在但不显示
|
||||
direction = 1, //1表示向右,-1表示向左,20表示向下,-20表示向上
|
||||
food = 43, //食物的位置
|
||||
n, //与下次移动的位置有关
|
||||
box = canvasRef.current && canvasRef.current.getContext('2d');
|
||||
//从0到399表示box里[0~19]*[0~19]的所有节点,每20px一个节点
|
||||
|
||||
function draw(seat, color) {
|
||||
box.fillStyle = color;
|
||||
box.fillRect(seat % 20 *20 + 1, ~~(seat / 20) * 20 + 1, 18, 18);
|
||||
//用color填充一个矩形,以前两个参数为x,y坐标,后两个参数为宽和高。
|
||||
}
|
||||
|
||||
document.onkeydown = function(evt) { //当键盘上下左右键摁下的时候改变direction
|
||||
direction = snake[1] - snake[0] == (n = [-1, -20, 1, 20][(evt || event).keyCode - 37] || direction) ? direction : n;
|
||||
console.log([-1, -20, 1, 20][(evt || event).keyCode - 37]);
|
||||
};
|
||||
|
||||
function _move() {
|
||||
snake.unshift(n = snake[0] + direction); //此时的n为下次蛇头出现的位置,n进入队列
|
||||
if(snake.indexOf(n, 1) > 0 || n < 0 || n > 399 || direction == 1 && n % 20 == 0 || direction == -1 && n % 20 == 19) {
|
||||
//if语句判断贪吃蛇是否撞到自己或者墙壁,碰到时返回,结束程序
|
||||
return alert("GAME OVER!");
|
||||
}
|
||||
draw(n, "lime"); //画出蛇头下次出现的位置
|
||||
if(n == food) { //如果吃到食物时,产生一个蛇身以外的随机的点,不会去掉蛇尾
|
||||
while (snake.indexOf(food = ~~(Math.random() * 400)) > 0);
|
||||
draw(food, "yellow");
|
||||
} else { //没有吃到食物时正常移动,蛇尾出队列
|
||||
draw(snake.pop(),"black");
|
||||
}
|
||||
setTimeout(() => _move(), 150); //每隔0.15秒执行函数一次,可以调节蛇的速度
|
||||
}
|
||||
|
||||
box && await _move()
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: '#000' }}>
|
||||
<button type="button" onClick={() => init()} >开始</button>
|
||||
<canvas ref={canvasRef} width="400" height="400" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 液体海报
|
||||
|
||||
<code src="./demos/demo1/index.jsx" />
|
||||
|
||||
### 大转盘(doing)
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
class Global {
|
||||
constructor () {};
|
||||
|
||||
/**
|
||||
* 判断是否为 PC 端,若是则返回 true,否则返回 flase
|
||||
*/
|
||||
IsPC() {
|
||||
let userAgentInfo = navigator.userAgent,
|
||||
flag = true,
|
||||
Agents = ["Android", "iPhone","SymbianOS", "Windows Phone","iPad", "iPod"];
|
||||
|
||||
for (let v = 0; v < Agents.length; v++) {
|
||||
if (userAgentInfo.indexOf(Agents[v]) > 0) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
|
||||
/**
|
||||
* 缓动函数,由快到慢
|
||||
* @param {Num} t 当前时间
|
||||
* @param {Num} b 初始值
|
||||
* @param {Num} c 变化值
|
||||
* @param {Num} d 持续时间
|
||||
*/
|
||||
easeOut(t, b, c, d) {
|
||||
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
|
||||
return -c / 2 * ((--t) * (t - 2) - 1) + b;
|
||||
};
|
||||
|
||||
windowToCanvas(canvas, e) {
|
||||
let bbox = canvas.getBoundingClientRect(),
|
||||
x = this.IsPC() ? e.clientX || event.clientX : e.changedTouches[0].clientX,
|
||||
y = this.IsPC() ? e.clientY || event.clientY : e.changedTouches[0].clientY;
|
||||
|
||||
return {
|
||||
x: x - bbox.left,
|
||||
y: y - bbox.top
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 绘制自动换行的文本
|
||||
* @param {Obj} context
|
||||
* @param {Str} t 文本内容
|
||||
* @param {Num} x 坐标
|
||||
* @param {Num} y 坐标
|
||||
* @param {Num} w 文本限制宽度
|
||||
* @param {Num} lineHeight 行高
|
||||
*/
|
||||
drawText(context, t, x, y, w, lineHeight = 20){
|
||||
let chr = t.split(''),
|
||||
temp = '',
|
||||
row = [];
|
||||
|
||||
for (let a = 0; a < chr.length; a++){
|
||||
if ( context.measureText(temp).width < w ) {
|
||||
;
|
||||
}
|
||||
else{
|
||||
row.push(temp);
|
||||
temp = '';
|
||||
}
|
||||
temp += chr[a];
|
||||
};
|
||||
|
||||
row.push(temp);
|
||||
|
||||
for(let b = 0; b < row.length; b++){
|
||||
context.fillText(row[b], x, y + (b + 1) * lineHeight);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 定义圆角矩形的方法
|
||||
* @param {Obj} context
|
||||
* @param {Num} cornerX
|
||||
* @param {Num} cornerY
|
||||
* @param {Num} width
|
||||
* @param {Num} height
|
||||
* @param {Num} cornerRadius
|
||||
*/
|
||||
roundedRect(context, cornerX, cornerY, width, height, cornerRadius) {
|
||||
if (width > 0) context.moveTo(cornerX + cornerRadius, cornerY);
|
||||
else context.moveTo(cornerX - cornerRadius, cornerY);
|
||||
|
||||
context.arcTo(cornerX + width, cornerY,
|
||||
cornerX + width, cornerY + height,
|
||||
cornerRadius);
|
||||
|
||||
context.arcTo(cornerX + width, cornerY + height,
|
||||
cornerX, cornerY + height,
|
||||
cornerRadius);
|
||||
|
||||
context.arcTo(cornerX, cornerY + height,
|
||||
cornerX, cornerY,
|
||||
cornerRadius);
|
||||
|
||||
if (width > 0) {
|
||||
context.arcTo(cornerX, cornerY,
|
||||
cornerX + cornerRadius, cornerY,
|
||||
cornerRadius);
|
||||
}
|
||||
else {
|
||||
context.arcTo(cornerX, cornerY,
|
||||
cornerX - cornerRadius, cornerY,
|
||||
cornerRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RouletteWheel extends Global{
|
||||
constructor(params) {
|
||||
super()
|
||||
this.width = params.width
|
||||
this.height = params.height
|
||||
|
||||
this.centerX = params.centerX
|
||||
this.centerY = params.centerY
|
||||
this.outsideRadius = params.outsideRadius
|
||||
|
||||
this.evenColor = params.evenColor
|
||||
this.oddColor = params.oddColor
|
||||
this.loseColor = params.odd
|
||||
this.textColor = params.textColor
|
||||
|
||||
this.awards = params.awards || []
|
||||
|
||||
this.startRadian = params.startRadian || 0
|
||||
this.duration = params.duration || 4000
|
||||
this.velocity = params.velocity || 10
|
||||
|
||||
// 回调函数
|
||||
this.finish = params.finish
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
let canvas = this.canvas
|
||||
canvas.width = this.width;
|
||||
canvas.height = this.height;
|
||||
let ctx = canvas.getContext('2d')
|
||||
|
||||
for (let i = 0; i < this.awards.length; i++) {
|
||||
// const award = awards[i]
|
||||
let _startR = this.startRadian + this.awardRadian * i
|
||||
let _endR = _startR + this.awardRadian
|
||||
|
||||
if (i % 2 === 0) ctx.fillStyle = "#FF6766"
|
||||
else ctx.fillStyle = "#FD5757"
|
||||
|
||||
ctx.beginPath(); //开始绘制路径
|
||||
ctx.moveTo(250, 250); //将当前位置移动到新的目标点
|
||||
ctx.arc(250, 250, this.radius, _startR, _endR);
|
||||
ctx.closePath(); //绘制路径
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.beginPath(); //开始绘制路径
|
||||
ctx.moveTo(250, 250); //将当前位置移动到新的目标点
|
||||
ctx.arc(250, 250, 250, Math.PI / 2, Math.PI);
|
||||
ctx.closePath(); //绘制路径
|
||||
ctx.fillStyle = "#ccc"; //填充背景颜色
|
||||
ctx.fill();
|
||||
ctx.beginPath(); //开始绘制路径
|
||||
ctx.moveTo(250, 250); //将当前位置移动到新的目标点
|
||||
ctx.arc(250, 250, 250, Math.PI, Math.PI * 1.5);
|
||||
ctx.closePath(); //绘制路径
|
||||
ctx.fillStyle = "#ddd"; //填充背景颜色
|
||||
ctx.fill();
|
||||
ctx.beginPath(); //开始绘制路径
|
||||
ctx.moveTo(250, 250); //将当前位置移动到新的目标点
|
||||
ctx.arc(250, 250, 250, Math.PI * 1.5, Math.PI * 2);
|
||||
ctx.closePath(); //绘制路径
|
||||
ctx.fillStyle = "#aaa"; //填充背景颜色
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
let rw = new RouletteWheel({
|
||||
canvas: canvasRef.current,
|
||||
width: '500',
|
||||
height: '500',
|
||||
awards: [ // 转盘内的奖品个数以及内容
|
||||
'大保健', '话费10元', '话费20元', '话费30元', '保时捷911', '周大福土豪金项链',
|
||||
// 'iphone 20', '火星7日游'
|
||||
]
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="200" height="200" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 火焰
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
function init() {
|
||||
var c = canvasRef.current,
|
||||
$ = c.getContext('2d'),
|
||||
w = c.width = window.innerWidth / 2,
|
||||
h = c.height = window.innerHeight / 2,
|
||||
particles = []
|
||||
|
||||
/**
|
||||
* 随机获取颜色
|
||||
*
|
||||
* @returns rgb(x,x,x)
|
||||
*/
|
||||
function randomColor() {
|
||||
var r = 100 + Math.floor(Math.random() * 255),
|
||||
g = Math.floor(Math.random() * 150),
|
||||
b = Math.floor(Math.random() * 15)
|
||||
return 'rgb(' + r + ',' + g + ',' + b + ')'
|
||||
}
|
||||
|
||||
function particle() {
|
||||
this.location = {
|
||||
x: w / 2,
|
||||
y: h / 2
|
||||
}
|
||||
this.speed = {
|
||||
x: -1.5 + Math.random() * 3,
|
||||
y: 1 + Math.random() * 5.5
|
||||
}
|
||||
this.life = 50
|
||||
this.radius = 1 + Math.floor(Math.random() * 25)
|
||||
this.color = randomColor()
|
||||
this.opacity = 1
|
||||
this.dead = false
|
||||
this.draw = function () {
|
||||
$.globalCompositeOperation = 'lighter'
|
||||
$.fillStyle = this.color
|
||||
$.beginPath()
|
||||
$.arc(this.location.x, this.location.y, this.radius, 0, Math.PI * 2)
|
||||
$.globalAlpha = this.opacity
|
||||
$.fill()
|
||||
$.closePath()
|
||||
}
|
||||
this.update = function () {
|
||||
if (this.location.x < 0 || this.life == 0 || this.opacity === 0 || this.radius < 1) {
|
||||
this.dead = true
|
||||
}
|
||||
if (!this.dead) {
|
||||
this.location.x += this.speed.x
|
||||
this.location.y -= this.speed.y
|
||||
this.life--
|
||||
this.opacity -= 0.05
|
||||
this.radius--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将火焰置于背景之后
|
||||
function stage() {
|
||||
$.globalCompositeOperation = 'source-over'
|
||||
$.fillStyle = 'rgba(0, 0, 0, 1)'
|
||||
$.fillRect(0, 0, w, h)
|
||||
}
|
||||
|
||||
// 重置画布大小
|
||||
function reset() {
|
||||
w = c.width = window.innerWidth / 2
|
||||
h = c.height = window.innerHeight / 2
|
||||
}
|
||||
|
||||
function loop() {
|
||||
stage()
|
||||
var L = particles.length
|
||||
if (L < 100) {
|
||||
particles.push(new particle())
|
||||
}
|
||||
for (var i = 0; i < L; i++) {
|
||||
var p = particles[i]
|
||||
p.draw()
|
||||
p.update()
|
||||
if (p.dead) {
|
||||
particles[i] = new particle()
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(loop)
|
||||
}
|
||||
|
||||
function _init() {
|
||||
reset()
|
||||
loop()
|
||||
}
|
||||
|
||||
window.addEventListener('resize', reset)
|
||||
_init()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
init()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="200" height="200" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 星空
|
||||
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* 星空初始化
|
||||
*/
|
||||
class NightSky {
|
||||
constructor(opt) {
|
||||
this.opt = {
|
||||
width: 500,
|
||||
height: 500,
|
||||
num: 120,
|
||||
canvas: null,
|
||||
...opt
|
||||
}
|
||||
this.opt.canvas.width = this.opt.width
|
||||
this.opt.canvas.height = this.opt.height
|
||||
this.ctx = this.opt.canvas && this.opt.canvas.getContext('2d')
|
||||
this.opt.canvas.style.backgroundColor = '#000'
|
||||
this.starList = []
|
||||
this.draw = this.draw
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.drawStar()
|
||||
this.animate()
|
||||
}
|
||||
|
||||
drawStar() {
|
||||
let { width, height, num } = this.opt
|
||||
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.starList[i] = new Star({
|
||||
maxRadius: 3,
|
||||
ctx: this.ctx,
|
||||
width,
|
||||
height
|
||||
})
|
||||
this.starList[i].draw()
|
||||
}
|
||||
}
|
||||
|
||||
animate() {
|
||||
let ctx = this.ctx
|
||||
let starList = this.starList
|
||||
let { width, height } = this.opt
|
||||
|
||||
function _move() {
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
for (const i in starList) {
|
||||
starList[i].move()
|
||||
}
|
||||
window.requestAnimationFrame(_move)
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(_move)
|
||||
}
|
||||
|
||||
draw(val) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
class Star {
|
||||
constructor(opt) {
|
||||
let { width, height, maxRadius = 2, ctx, speed = 0.5 } = opt
|
||||
this.x = Math.random() * width
|
||||
this.y = Math.random() * height
|
||||
this.height = height
|
||||
this.width = width
|
||||
this.speed = speed
|
||||
this.maxRadius = maxRadius
|
||||
this.ctx = ctx
|
||||
this.r = Math.random() * maxRadius
|
||||
var alpha = (Math.floor(Math.random() * 10) + 1) / 10
|
||||
this.color = `rgba(255, 255, 255, ${alpha})`
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.ctx.fillStyle = this.color
|
||||
this.ctx.shadowBlur = this.r * 2
|
||||
this.ctx.beginPath()
|
||||
this.ctx.arc(this.x, this.y, this.r * Math.random(), 0, 2 * Math.PI, false)
|
||||
this.ctx.closePath()
|
||||
this.ctx.fill()
|
||||
}
|
||||
|
||||
move() {
|
||||
this.y -= this.speed
|
||||
if (this.y <= -10) {
|
||||
this.y = this.height + 10
|
||||
}
|
||||
this.draw()
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
let nightSky = new NightSky({
|
||||
canvas: canvasRef.current,
|
||||
width: 500,
|
||||
height: 300
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="200" height="200" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 移动(doing)
|
||||
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
class Move {
|
||||
constructor(opt) {
|
||||
const option = {
|
||||
canvas: null,
|
||||
width: document.documentElement.clientWidth, // 宽度
|
||||
height: document.documentElement.clientHeight, // 高度
|
||||
bgColor: '#000',
|
||||
para: {
|
||||
num: 100,
|
||||
color: false, // 颜色 如果是false 则是随机渐变颜色
|
||||
r: 0.9, // 圆每次增加的半径
|
||||
o: 0.09, // 判断圆消失的条件,数值越大,消失的越快
|
||||
},
|
||||
...opt
|
||||
}
|
||||
const { canvas, width, height, bgColor } = option
|
||||
this.option = option
|
||||
this.round_arr = []
|
||||
this.ctx = canvas.getContext('2d')
|
||||
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
canvas.style.backgroundColor = bgColor
|
||||
|
||||
this.init(this)
|
||||
}
|
||||
|
||||
init(opt) {
|
||||
let tempSum = 0
|
||||
window.onmousemove = function (event) {
|
||||
|
||||
let mouseX = event.clientX;
|
||||
let mouseY = event.clientY;
|
||||
|
||||
if (tempSum < 5) {
|
||||
tempSum++
|
||||
} else {
|
||||
opt.round_arr.push({
|
||||
mouseX,
|
||||
mouseY,
|
||||
r: opt.option.para.r, // 设置半径每次增大的数值
|
||||
o: 1, // 判断圆消失的条件,数值越大,消失得越快
|
||||
})
|
||||
tempSum = 0
|
||||
opt.animate()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
animate() {
|
||||
let { para, width, height } = this.option
|
||||
let color = 0, color2
|
||||
let ctx = this.ctx
|
||||
let round_arr = this.round_arr
|
||||
|
||||
if (!para.color) {
|
||||
color += Math.random();
|
||||
color2 = 'hsl(' + color + ',100%,80%)';
|
||||
}
|
||||
|
||||
function _move() {
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
for (var i = 0; i < round_arr.length; i++) {
|
||||
|
||||
ctx.fillStyle = color2;
|
||||
ctx.beginPath();
|
||||
ctx.arc( round_arr[i].mouseX ,round_arr[i].mouseY, round_arr[i].r, 0, Math.PI * 2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
round_arr[i].r += para.r;
|
||||
round_arr[i].o -= para.o;
|
||||
|
||||
if( round_arr[i].o <= 0){
|
||||
round_arr.splice(i,1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(_move);
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
let rw = new Move({
|
||||
canvas: canvasRef.current,
|
||||
width: 500,
|
||||
height: 300
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="200" height="200" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 棒棒糖
|
||||
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
class Lollipop {
|
||||
constructor(opt) {
|
||||
this.opt = {
|
||||
canvas: null, // 画布
|
||||
width: document.documentElement.clientWidth, // 宽度
|
||||
height: document.documentElement.clientHeight, // 高度
|
||||
bgColor: '#000',
|
||||
...opt
|
||||
}
|
||||
this.ctx = this.opt.canvas.getContext('2d')
|
||||
|
||||
// 初始化画布
|
||||
this.opt.canvas.width = this.opt.width
|
||||
this.opt.canvas.height = this.opt.height
|
||||
this.opt.canvas.style.backgroundColor = this.opt.bgColor
|
||||
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
this._drawCircle(this.ctx)
|
||||
this._drawStick(this.ctx)
|
||||
this._drawHalfCircle(this.ctx)
|
||||
}
|
||||
|
||||
/**
|
||||
* 画圆
|
||||
* @param {*} ctx
|
||||
*/
|
||||
_drawCircle(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(300, 300, 50, 0, Math.PI * 2, true)
|
||||
ctx.closePath()
|
||||
ctx.fillStyle = '#fff'
|
||||
ctx.shadowBlur = 15
|
||||
ctx.shadowColor = '#fff'
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
/**
|
||||
* 棍子
|
||||
* @param {*} ctx
|
||||
*/
|
||||
_drawStick(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(340, 340)
|
||||
ctx.lineTo(450, 450)
|
||||
ctx.lineWidth = 8
|
||||
ctx.lineCap = 'round'
|
||||
ctx.strokeStyle = '#fff'
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
_drawHalfCircle(ctx) {
|
||||
ctx.beginPath()
|
||||
ctx.arc(300, 300, 30, 0, Math.PI * 0.6, false)
|
||||
ctx.shadowBlur = 5
|
||||
ctx.lineWidth = 5
|
||||
ctx.lineCap = 'round'
|
||||
ctx.strokeStyle = '#ccc'
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
new Lollipop({
|
||||
canvas: canvasRef.current,
|
||||
width: 500,
|
||||
height: 800
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} width="200" height="200" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 行星动画
|
||||
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
const solarRef = useRef()
|
||||
|
||||
function init() {
|
||||
let sun = new Image()
|
||||
let moon = new Image()
|
||||
let earth = new Image()
|
||||
sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png'
|
||||
moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png'
|
||||
earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png'
|
||||
|
||||
window.requestAnimationFrame(solar)
|
||||
|
||||
// 太阳系动画
|
||||
function solar() {
|
||||
let canvas = solarRef && solarRef.current
|
||||
let ctx = canvas.getContext('2d')
|
||||
|
||||
// 将目标图形置于上层
|
||||
ctx.globalCompositeOperation = 'destination-over'
|
||||
ctx.clearRect(0, 0, 300, 300)
|
||||
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'
|
||||
ctx.strokeStyle = 'rgba(0, 153, 255, 0.4)'
|
||||
|
||||
// 保存当前状态
|
||||
ctx.save()
|
||||
|
||||
// 将当前画笔移到圆心处
|
||||
ctx.translate(150, 150)
|
||||
|
||||
let time = new Date()
|
||||
|
||||
// 地球顺时针旋转的角度
|
||||
ctx.rotate(((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds())
|
||||
// 从当前圆心处 x 偏移量
|
||||
ctx.translate(105, 0)
|
||||
// 绘制当图片加载不出来 默认图
|
||||
// ctx.fillRect(0, -12, 25, 25)
|
||||
// 绘制地球的图片
|
||||
ctx.drawImage(earth, -12, -12)
|
||||
|
||||
// 保存当前位置
|
||||
// 基于地球为圆心的旋转角度
|
||||
ctx.rotate(((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds())
|
||||
// 以地球为圆心的偏移
|
||||
ctx.translate(0, 28.5)
|
||||
// 绘制月亮
|
||||
ctx.drawImage(moon, -3.5, -3.5)
|
||||
// 加载上一次的 save
|
||||
ctx.restore()
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.arc(150, 150, 105, 0, Math.PI * 2, false)
|
||||
ctx.stroke()
|
||||
|
||||
ctx.drawImage(sun, 0, 0, 300, 300)
|
||||
|
||||
window.requestAnimationFrame(solar)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => init(), 1000)
|
||||
}, [solarRef])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={solarRef} width="300" height="300" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 时钟
|
||||
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
const clockRef = useRef()
|
||||
|
||||
function clock() {
|
||||
var theCanv = clockRef.current;
|
||||
var theCanvObject = theCanv.getContext('2d');
|
||||
var x = 200;
|
||||
var y = 200;
|
||||
|
||||
startTime();
|
||||
|
||||
function startTime() {
|
||||
|
||||
//分秒刻度和表盘
|
||||
theCanvObject.lineWidth = 1;
|
||||
for (var i = 0; i < 60; i++) {
|
||||
drawArc(150, i*6, (i+1)*6);
|
||||
}
|
||||
drawArc(145, 0, 360, true);
|
||||
|
||||
//时刻度
|
||||
theCanvObject.lineWidth = 2;
|
||||
for (var i = 0; i < 12; i++) {
|
||||
drawArc(150, i*30, (i+1)*30);
|
||||
}
|
||||
drawArc(140, 0, 360, true);
|
||||
|
||||
//针
|
||||
drawHand(getTime().hour,5,60,'#ECFC00');
|
||||
drawHand(getTime().min,4,100,'#00BB3F');
|
||||
drawHand(getTime().sec,3,130,'#D60062');
|
||||
|
||||
setInterval(function () {
|
||||
drawArc(135,0,360,true);
|
||||
drawHand(getTime().hour,5,60,'#ECFC00');
|
||||
drawHand(getTime().min,4,100,'#00BB3F');
|
||||
drawHand(getTime().sec,3,130,'#D60062');
|
||||
},1000);
|
||||
}
|
||||
|
||||
function drawArc(iRadius, iBeginAngle, iEndAngle, ifClear) {
|
||||
var beginRadian = iBeginAngle*Math.PI/180;
|
||||
var endRadian = iEndAngle*Math.PI/180;
|
||||
|
||||
theCanvObject.beginPath(); //创建一个路径
|
||||
theCanvObject.moveTo(x, y); //将路径移到x,y
|
||||
theCanvObject.arc(x, y, iRadius, beginRadian, endRadian, false);
|
||||
//画弧
|
||||
!ifClear && theCanvObject.stroke();
|
||||
|
||||
if (ifClear) {
|
||||
theCanvObject.fillStyle = 'white';
|
||||
theCanvObject.fill();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function drawHand(iAngle, iWidth, iLength, iColor) {
|
||||
|
||||
theCanvObject.save(); //保存的是canvas的属性,不是截图
|
||||
theCanvObject.lineWidth = iWidth;
|
||||
theCanvObject.strokeStyle = iColor;
|
||||
drawArc(iLength, iAngle, iAngle);
|
||||
theCanvObject.restore(); //弹出栈中的状态
|
||||
|
||||
}
|
||||
|
||||
//根据当前时间返回各个针要指的度数
|
||||
function getTime() {
|
||||
|
||||
var jTime = {};
|
||||
var iNow = new Date();
|
||||
jTime.sec = -90 + iNow.getSeconds()*6;
|
||||
jTime.min = -90 + iNow.getMinutes()*6 + iNow.getSeconds()/20;
|
||||
jTime.hour = -90 + iNow.getHours()*30 + iNow.getMinutes()/2;
|
||||
|
||||
return jTime;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
clock()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={clockRef} width="600" height="600" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
Before Width: | Height: | Size: 166 KiB |
Before Width: | Height: | Size: 150 KiB |
@ -1,118 +0,0 @@
|
||||
import React, { useRef, useEffect, FC } from 'react';
|
||||
import './index.less'
|
||||
|
||||
const img1 = require('./img/clipImg1.jpg')
|
||||
const img2 = require('./img/clipImg2.jpg')
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
const clipImgs1Ref = useRef()
|
||||
const clipImgs2Ref = useRef()
|
||||
|
||||
|
||||
function clipPathMaskRender() {
|
||||
const NUM_CIRCLES = 60
|
||||
const MIN_SIZE = 50
|
||||
const MAX_SIZE = 100
|
||||
|
||||
function getRndInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
var c = canvasRef && canvasRef.current
|
||||
var ctx = c.getContext('2d')
|
||||
var clipImg1 = clipImgs1Ref && clipImgs1Ref.current
|
||||
var clipImg2 = clipImgs2Ref && clipImgs2Ref.current
|
||||
var t
|
||||
|
||||
class Circle {
|
||||
constructor() {
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.size = 0
|
||||
this._needsRandomized = false
|
||||
}
|
||||
|
||||
randomize () {
|
||||
this.x = getRndInt(50, c.width - 50)
|
||||
this.y = getRndInt(50, c.height - 50)
|
||||
this.maxSize = getRndInt(MIN_SIZE, MAX_SIZE)
|
||||
}
|
||||
|
||||
// 更新
|
||||
update (t, ofs) {
|
||||
// abs 绝对值
|
||||
this.size = Math.abs(Math.round(Math.sin(t + ofs) * this.maxSize))
|
||||
|
||||
if (this.size < 2) {
|
||||
if (this._needsRandomized) {
|
||||
this.randomize()
|
||||
this._needsRandomized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 画
|
||||
draw () {
|
||||
ctx.moveTo(this.x, this.y)
|
||||
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI)
|
||||
}
|
||||
}
|
||||
|
||||
var circles =[]
|
||||
for (let i = 0; i < NUM_CIRCLES; i++) {
|
||||
var circle = new Circle()
|
||||
circle.randomize()
|
||||
circles.push(circle)
|
||||
}
|
||||
|
||||
function update() {
|
||||
t = 0.001 * Date.now()
|
||||
circles.forEach((circle, idx) => {
|
||||
circle.update(t, idx)
|
||||
})
|
||||
}
|
||||
|
||||
async function render() {
|
||||
await ctx.drawImage(clipImg1, 0, 0)
|
||||
await ctx.save()
|
||||
await ctx.beginPath()
|
||||
|
||||
circles.forEach(function(circle) {
|
||||
circle.draw()
|
||||
})
|
||||
|
||||
ctx.closePath()
|
||||
ctx.clip()
|
||||
|
||||
ctx.drawImage(clipImg2, 0, 0)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
function loop() {
|
||||
requestAnimationFrame(loop)
|
||||
update()
|
||||
render()
|
||||
}
|
||||
|
||||
loop()
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
clipPathMaskRender()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="clipPathMask demo">
|
||||
<div className="wrap">
|
||||
<h1 className="wrap_tit">Nice Note</h1>
|
||||
<canvas ref={canvasRef} className="wrap_canvas" width="500px" height="500px"></canvas>
|
||||
</div>
|
||||
<div id="clipImgs">
|
||||
<img ref={clipImgs1Ref} id="clipImgs_1" crossOrigin="anonymous" src={img1} />
|
||||
<img ref={clipImgs2Ref} id="clipImgs_2" crossOrigin="anonymous" src={img2} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
.clipPathMask {
|
||||
text-align: center;
|
||||
height: 80vh;
|
||||
color: #fff;
|
||||
background: teal linear-gradient(transparent, #ff0099);
|
||||
.wrap {
|
||||
&_tit {
|
||||
padding: 10px 0;
|
||||
font-size: 30px;
|
||||
}
|
||||
&_canvas {
|
||||
border: 1px solid yellow;
|
||||
}
|
||||
}
|
||||
#clipImgs {
|
||||
display: none;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 10 KiB |
@ -1,217 +0,0 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import img1 from './imgs/1.jpg'
|
||||
import img2 from './imgs/2.jpg'
|
||||
import img3 from './imgs/3.jpg'
|
||||
|
||||
const imgs = [img1, img2, img3]
|
||||
class Particale {
|
||||
constructor(opt) {
|
||||
this.warp = opt.warp; //画布
|
||||
this.ctx = opt.warp && opt.warp.getContext('2d');
|
||||
this.imgsUrl = opt.imgsUrl; //图片地址数组
|
||||
this.imgsObj = []; //图片对象数组
|
||||
this.radius = opt.radius || 10; //粒子半径
|
||||
this.index = 0; //当前图片下标
|
||||
this.initz = 300;
|
||||
this.dots = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
//画布居中
|
||||
this.warp.style.marginLeft = "calc(50vw - 500px)";
|
||||
this.warp.style.marginTop = "calc(50vh - 300px)";
|
||||
|
||||
//限制小球半径
|
||||
if (this.warp.width > 500 || this.warp.height > 300)
|
||||
this.radius >= 4 ? this.radius = this.radius : this.radius = 4;
|
||||
else
|
||||
this.radius >= 2 ? this.radius = this.radius : this.radius = 2;
|
||||
|
||||
let promiseArr = this.imgsUrl.map(imgUrl => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var imgObj = new Image();
|
||||
imgObj.onload = () => {
|
||||
this.imgsObj.push(imgObj);
|
||||
resolve();
|
||||
};
|
||||
imgObj.src = imgUrl;
|
||||
});
|
||||
});
|
||||
//图片全部加载完毕开始绘制
|
||||
Promise.all(promiseArr).then(() => {
|
||||
this.picLoop();
|
||||
});
|
||||
}
|
||||
|
||||
picLoop() {
|
||||
this.dots = [];
|
||||
this.drawPic(); //绘制当前图片
|
||||
this.toParticle(); //得到像素点
|
||||
this.combineAnimate(); //合成图像
|
||||
this.index === this.imgsUrl.length-1 ? this.index = 0 : this.index++; //下标移动到下一张图片
|
||||
}
|
||||
drawPic() {
|
||||
//清除画布
|
||||
this.ctx.clearRect(0, 0, this.warp.width, this.warp.height);
|
||||
let imgObj = this.imgsObj[this.index];
|
||||
|
||||
//限制图片大小
|
||||
if(imgObj.width > imgObj.height) {
|
||||
let ImgScale = imgObj.height / imgObj.width;
|
||||
imgObj.width = this.warp.width * .5;
|
||||
imgObj.height = imgObj.width * ImgScale;
|
||||
} else {
|
||||
let ImgScale = imgObj.width / imgObj.height;
|
||||
imgObj.height = this.warp.height * .7;
|
||||
imgObj.width = imgObj.height * ImgScale;
|
||||
}
|
||||
|
||||
|
||||
//绘制图片到canvas
|
||||
this.ctx.drawImage(imgObj, this.warp.width / 2 - imgObj.width / 2, this.warp.height / 2 - imgObj.height / 2, imgObj.width, imgObj.height);
|
||||
|
||||
}
|
||||
toParticle() {
|
||||
//得到像素
|
||||
let imageData = this.ctx.getImageData(0, 0, this.warp.width, this.warp.height);
|
||||
let data = imageData.data;
|
||||
|
||||
for(let x = 0; x < imageData.width; x += this.radius * 2) {
|
||||
for(let y = 0; y < imageData.height; y += this.radius * 2) {
|
||||
let i = (x + y * this.warp.width) * 4;
|
||||
if(data[i+3] !== 0 && data[i] !== 255 && data[i+1] !== 255 && data[i+2] !== 255) {
|
||||
let dot = {
|
||||
x: x, //图片x轴坐标
|
||||
y: y, // y轴坐标
|
||||
z: 0, // z轴坐标
|
||||
r: data[i], // rgba
|
||||
g: data[i+1], // rgba
|
||||
b: data[i+2], // rgba
|
||||
a: 1, // rgba
|
||||
ix: Math.random() * this.warp.width, //初始化x轴坐标
|
||||
iy: Math.random() * this.warp.height, // y轴坐标
|
||||
iz: Math.random() * this.initz * 2 - this.initz, // z轴坐标
|
||||
ir: 255, // rgba
|
||||
ig: 255, // rgba
|
||||
ib: 255, // rgba
|
||||
ia: 0, // rgba
|
||||
tx: Math.random() * this.warp.width, //目标x轴坐标
|
||||
ty: Math.random() * this.warp.height, // y轴坐标
|
||||
tz: Math.random() * this.initz * 2 - this.initz, // z轴坐标
|
||||
tr: 255, // rgba
|
||||
tg: 255, // rgba
|
||||
tb: 255, // rgba
|
||||
ta: 0, // rgba
|
||||
};
|
||||
this.dots.push(dot);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
combineAnimate() {
|
||||
let combined = false;
|
||||
this.ctx.clearRect(0, 0, this.warp.width, this.warp.height);
|
||||
this.dots.map(dot => {
|
||||
if (Math.abs(dot.ix - dot.x) < 0.1 && Math.abs(dot.iy - dot.y) < 0.1 && Math.abs(dot.iz - dot.z) < 0.1) {
|
||||
dot.ix = dot.x;
|
||||
dot.iy = dot.y;
|
||||
dot.iz = dot.z;
|
||||
dot.ir = dot.r;
|
||||
dot.ig = dot.g;
|
||||
dot.ib = dot.b;
|
||||
dot.ia = dot.a;
|
||||
combined = true;
|
||||
} else {
|
||||
dot.ix += (dot.x - dot.ix) * 0.07;
|
||||
dot.iy += (dot.y - dot.iy) * 0.07;
|
||||
dot.iz += (dot.z - dot.iz) * 0.07;
|
||||
dot.ir += (dot.r - dot.ir) * 0.3;
|
||||
dot.ig += (dot.g - dot.ig) * 0.3;
|
||||
dot.ib += (dot.b - dot.ib) * 0.3;
|
||||
dot.ia += (dot.a - dot.ia) * 0.1;
|
||||
combined = false;
|
||||
}
|
||||
|
||||
return this.drowDot(dot);
|
||||
});
|
||||
|
||||
|
||||
if(!combined) {
|
||||
requestAnimationFrame(() => {
|
||||
return this.combineAnimate();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
return this.separateAnimate();
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
separateAnimate() {
|
||||
let separated = false;
|
||||
this.ctx.clearRect(0, 0, this.warp.width, this.warp.height);
|
||||
this.dots.map(dot => {
|
||||
if (Math.abs(dot.ix - dot.tx) < 0.1 && Math.abs(dot.iy - dot.ty) < 0.1 && Math.abs(dot.iz - dot.tz) < 0.1) {
|
||||
dot.ix = dot.tx;
|
||||
dot.iy = dot.ty;
|
||||
dot.iz = dot.tz;
|
||||
dot.ir = dot.tr;
|
||||
dot.ig = dot.tg;
|
||||
dot.ib = dot.tb;
|
||||
dot.ia = dot.ta;
|
||||
separated = true;
|
||||
} else {
|
||||
dot.ix += (dot.tx - dot.ix) * 0.07;
|
||||
dot.iy += (dot.ty - dot.iy) * 0.07;
|
||||
dot.iz += (dot.tz - dot.iz) * 0.07;
|
||||
dot.ir += (dot.tr - dot.ir) * 0.02;
|
||||
dot.ig += (dot.tg - dot.ig) * 0.02;
|
||||
dot.ib += (dot.tb - dot.ib) * 0.02;
|
||||
dot.ia += (dot.ta - dot.ia) * 0.03;
|
||||
separated = false;
|
||||
}
|
||||
|
||||
return this.drowDot(dot);
|
||||
});
|
||||
|
||||
|
||||
if(!separated) {
|
||||
requestAnimationFrame(() => {
|
||||
return this.separateAnimate();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
return this.picLoop(); //间接递归,使用尾递归优化
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
drowDot(dot) {
|
||||
let scale = this.initz / (this.initz + dot.iz);
|
||||
this.ctx.save();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.fillStyle = `rgba(${Math.floor(dot.ir)}, ${Math.floor(dot.ig)}, ${Math.floor(dot.ib)}, ${dot.ia})`;
|
||||
this.ctx.arc(this.warp.width / 2 + (dot.ix - this.warp.width / 2) * scale, this.warp.height / 2 + (dot.iy - this.warp.height / 2) * scale, this.radius * scale, 0, Math.PI * 2);
|
||||
this.ctx.fill();
|
||||
this.ctx.closePath();
|
||||
this.ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
useEffect(() => {
|
||||
if (canvasRef && canvasRef.current) {
|
||||
new Particale({
|
||||
warp: canvasRef && canvasRef.current,
|
||||
imgsUrl: imgs,
|
||||
radius: 1,
|
||||
});
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<canvas ref={canvasRef} width="500px" height="500px"></canvas>
|
||||
)
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
const canvasRef = useRef()
|
||||
|
||||
function init() {
|
||||
const theCanvas = canvasRef.current,
|
||||
ctx = theCanvas.getContext('2d'),
|
||||
current_point = {
|
||||
x: null, //当前鼠标x
|
||||
y: null, //当前鼠标y
|
||||
max: 20000,
|
||||
};
|
||||
|
||||
let canvas_width = theCanvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
|
||||
canvas_height = theCanvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
|
||||
random_points = [],
|
||||
all_points = [];
|
||||
|
||||
theCanvas.style = "position: absolute; top: 0px; left: 0px;";
|
||||
|
||||
|
||||
function draw() {
|
||||
//清屏
|
||||
ctx.clearRect(0, 0, canvas_width, canvas_height);
|
||||
let i,pi,x_dist,y_dist,dist,w;
|
||||
|
||||
//遍历点集合绘制线条,类似于握手问题,两个点只绘制一条线
|
||||
random_points.forEach((p, index) => {
|
||||
p.x += p.xa, //按指定速度移动
|
||||
p.y += p.ya,
|
||||
//小球碰撞则速度取相反数
|
||||
p.xa *= p.x > canvas_width || p.x < 0 ? -1 : 1,
|
||||
p.ya *= p.y > canvas_height || p.y < 0 ? -1 : 1,
|
||||
ctx.fillRect(p.x - 0.5, p.y - 0.5, 1, 1); //绘制点
|
||||
|
||||
for(i = index + 1; i < all_points.length; i++ ) {
|
||||
pi = all_points[i];
|
||||
if(pi.x !== null && pi.y !== null) {
|
||||
x_dist = p.x - pi.x;
|
||||
y_dist = p.y - pi.y;
|
||||
dist = x_dist * x_dist + y_dist * y_dist;
|
||||
//当两点距离小于极限距离时会产生连线,当第二个点是鼠标所产生点时,第一个点在范围内会产生向鼠标点的速度,产生吸附效果
|
||||
dist < pi.max && (pi === current_point && dist >= pi.max / 2 && (p.x -= 0.03 * x_dist, p.y -= 0.03 * y_dist));
|
||||
//根据距离计算连线的透明度,使过度效果流畅
|
||||
w = (pi.max - dist) / pi.max;
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = w / 2;
|
||||
ctx.strokeStyle = `rgba(110,110,110,${w + 0.2})`;
|
||||
ctx.moveTo(p.x, p.y);
|
||||
ctx.lineTo(pi.x, pi.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}),requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
|
||||
//绑定事件,判断是否添加鼠标这个点
|
||||
window.onmousemove = e => {
|
||||
e = e || window.event;
|
||||
current_point.x = e.clientX;
|
||||
current_point.y = e.clientY;
|
||||
};
|
||||
window.onmouseout = () => {
|
||||
current_point.x = null;
|
||||
current_point.y = null;
|
||||
};
|
||||
|
||||
|
||||
//随机生成100个点
|
||||
for(let i = 0; i < 100; i++ ) {
|
||||
|
||||
let x = Math.random() * canvas_width, //初始坐标
|
||||
y = Math.random() * canvas_height,
|
||||
xa = 2 * Math.random() - 1, //x速度
|
||||
ya = 2 * Math.random() - 1, //y速度
|
||||
max = 6000; //会产生连线的距离的平方
|
||||
|
||||
random_points[i] = {x, y, xa, ya, max};
|
||||
}
|
||||
//将鼠标的点添加至点集合中
|
||||
all_points = [...random_points,current_point];
|
||||
|
||||
//只是背景特效-所以延迟执行
|
||||
setTimeout(draw, 100);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
init()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<canvas ref={canvasRef} width="500px" height="500px"></canvas>
|
||||
</div>
|
||||
)
|
||||
}
|
52
docs/fea/svg/demos/index.less
Normal file
@ -0,0 +1,52 @@
|
||||
.ok {
|
||||
&_line {
|
||||
stroke-dasharray:300,300;
|
||||
stroke-dashoffset:300;
|
||||
transition: all 2s linear;
|
||||
}
|
||||
&_checkmark {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
stroke-width: 2;
|
||||
stroke: #fff;
|
||||
margin: 10% auto;
|
||||
box-shadow: inset 0px 0px 0px #7ac142;
|
||||
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
|
||||
&__circle {
|
||||
stroke-dasharray: 166;
|
||||
stroke-dashoffset: 166;
|
||||
stroke-width: 2;
|
||||
stroke: #7ac142;
|
||||
fill: none;
|
||||
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
||||
}
|
||||
&__check {
|
||||
transform-origin: 50% 50%;
|
||||
stroke-dasharray: 48;
|
||||
stroke-dashoffset: 0;
|
||||
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
||||
}
|
||||
:global {
|
||||
@keyframes stroke {
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
@keyframes fill {
|
||||
100% {
|
||||
box-shadow: inset 0px 0px 0px 30px #7ac142;
|
||||
}
|
||||
}
|
||||
@keyframes scale {
|
||||
0,100% {
|
||||
transform: none;
|
||||
}
|
||||
50% {
|
||||
transform: scale3d(1.1, 1.1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
docs/fea/svg/demos/ok.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import styles from './index.less'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div className={styles.ok}>
|
||||
<svg x="0px" y="0px" width="300px" height="100px" viewBox="0 0 300 100" class="svg1">
|
||||
<line className={styles.ok_line} x1="20" y1="50" x2="200" y2="50" stroke="#000" stroke-width="1" ></line>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||
<circle class={styles.ok_checkmark__circle} cx="26" cy="26" r="25" fill="none"/>
|
||||
<path className={styles.ok_checkmark__check} fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
36
docs/fea/svg/index.md
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
nav:
|
||||
title: 前端
|
||||
path: /fea
|
||||
group:
|
||||
title: 💊 SVG
|
||||
order: 3
|
||||
path: /svg
|
||||
---
|
||||
|
||||
## 基本介绍
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<svg x="0px" y="0px" width="500px" height="360px" viewBox="0 0 500 100">
|
||||
<rect x="10" y="5" fill="white" stroke="green" width="90" height="90"></rect>
|
||||
<circle cx="170" cy="50" r="45" fill="black" stroke="red" ></circle>
|
||||
<line fill="none" stroke="black" x1="230" y1="6" x2="260" y2="95"></line>
|
||||
<ellipse cx="400" cy="50" rx="60" ry="30" />
|
||||
<g>
|
||||
<path id="svg_5" d="m148.60774,139.10039c28.24222,-69.1061 138.89615,0 0,88.8507c-138.89615,-88.8507 -28.24222,-157.9568 0,-88.8507z" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="red"/>
|
||||
<path id="svg_6" d="m265.00089,146.09396l19.88082,-21.09307l21.11909,22.40665l21.11909,-22.40665l19.88101,21.09307l-21.11909,22.40684l21.11909,22.40684l-19.88101,21.09326l-21.11909,-22.40684l-21.11909,22.40684l-19.88082,-21.09326l21.11891,-22.40684l-21.11891,-22.40684z" fill-opacity="null" stroke-opacity="null" stroke-width="1.5" stroke="#000" fill="#ccc"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 基础动画案例
|
||||
|
||||
<code src="./demos/ok.tsx" />
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
hero:
|
||||
title: Nice Note
|
||||
desc: 💊 Dev's 学习笔记
|
||||
desc: 💊 Dev's 笔记
|
||||
actions:
|
||||
- text: 开始学习
|
||||
link: /fea/website
|
||||
|
@ -1,7 +1,4 @@
|
||||
---
|
||||
# nav:
|
||||
# title: 其它
|
||||
# path: /other
|
||||
group:
|
||||
title: 💊 买房攻略 2017
|
||||
order: 3
|
||||
|
@ -1,7 +1,4 @@
|
||||
---
|
||||
# nav:
|
||||
# title: 其它
|
||||
# path: /other
|
||||
group:
|
||||
title: 💊 买房攻略 2020
|
||||
order: 1
|
||||
|
@ -32,3 +32,15 @@ Linux卸载脚本:
|
||||
```
|
||||
rm HomebrewUninstall.sh ; wget https://gitee.com/cunkai/HomebrewCN/raw/master/HomebrewUninstall.sh ; bash HomebrewUninstall.sh
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
* 搜索包:brew search [package-name]
|
||||
* 查看包的信息:brew info [package-name]
|
||||
* 安装终端应用\环境:brew install [package-name]
|
||||
* 安装GUI应用 brew install --cask [AppName]
|
||||
* 更新所有包:brew update
|
||||
* 更新某个包:brew upgrade [package-name]
|
||||
* 卸载某个包:brew uninstall [package-name]
|
||||
* 列出所有安装的包:brew list
|
||||
* 清理所有过时软件:brew cleanup
|
29
src/index.md
@ -3,9 +3,34 @@ nav:
|
||||
title: FC
|
||||
path: /funny
|
||||
group:
|
||||
title: 💊
|
||||
title: 说明
|
||||
order: 3
|
||||
path: /code
|
||||
---
|
||||
|
||||
就是一些有意思的 code
|
||||
# 说明
|
||||
|
||||
就是一些有意思的酷炫 code
|
||||
|
||||
## 迪士尼动画12原则
|
||||
|
||||
1. 挤压与拉伸
|
||||
2. 预备动作
|
||||
3. 表情与呈像方式
|
||||
4. 逐帧画法与关键帧画法
|
||||
5. 动作的惯性跟随和重叠
|
||||
6. 慢入与慢出
|
||||
7. 弧形运动轨迹
|
||||
8. 次要动作
|
||||
9. 节奏
|
||||
10. 夸张
|
||||
11. 熟练的手绘技法
|
||||
12. 吸引力
|
||||
|
||||
## 提升页面性能技巧
|
||||
|
||||
页面渲染顺序:js - css - layout(重排) - paint(重绘) - composite
|
||||
|
||||
* translate 替换为 left\top\right\bottom
|
||||
* scale 替换为 width\height
|
||||
* opacity 替换为 display\visibility
|
||||
|