Canvas實現(xiàn)數(shù)字雨和放大鏡效果的代碼示例
正文
還是先來看看效果
- 數(shù)字雨

- 放大鏡

數(shù)字雨
我認為數(shù)字雨的核心在于單條數(shù)字雨生成和數(shù)字雨移動
事前先準備幾個函數(shù),方便我們的操作,隨機數(shù)可以用來生成數(shù)字雨的坐標,文字的大小以及移動的速度。
/** 生成隨機數(shù)*/
const createRandomNum = useCallback((min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}, [])
/**
* @description 創(chuàng)建01字符串
* @returns 01字符串
*/
function generateRandomString() {
const characters = '01';
const len = Math.floor(Math.random() * (60 - 45 + 1)) + 45;
let randomString = '';
for (let i = 0; i < len; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
randomString += characters[randomIndex];
}
return randomString;
}生成單條數(shù)字雨

單條的樣式就出來了

由于我們接下來要對它進行移動,那我們必須記錄數(shù)字雨的相關(guān)信息,然后對坐標進行修改,最后再繪制就可以了。
interface List {
x: number,
y: number,
text: string,
fontSize: number,
width: number,
speed: number,
}在我們每生成一條數(shù)字雨時就去記錄對應數(shù)據(jù)

再結(jié)合requestAnimationFrame,我們的動畫效果就出來了

放大鏡
放大鏡比數(shù)字雨還簡單,一共就兩步,獲取有效的鼠標坐標和drawImage切圖并繪制

代碼
數(shù)字雨
import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import './DigitalRain.scss'
interface List {
x: number,
y: number,
text: string,
fontSize: number,
width: number,
speed: number,
}
export default function Rain() {
const canvasDom = useRef<any>(null)
const canvasCtx = useRef<any>(null)
const [width, setWidth] = useState(0)
const [height, setHeight] = useState(0)
/** 數(shù)字雨總數(shù)*/
const amount = useRef(100);
/** 已存在的數(shù)字雨*/
const dataList = useRef<List[]>([])
const animation = useRef<any>(null)
/** 設置canvas的寬高*/
const setCanvasSize = useCallback(() => {
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
setHeight(screenHeight)
setWidth(screenWidth)
}, [])
/** 生成隨機數(shù)*/
const createRandomNum = useCallback((min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}, [])
useEffect(() => {
if (canvasDom.current === null) {
return
}
setCanvasSize()
canvasCtx.current = canvasDom.current.getContext('2d');
window.addEventListener('resize', setCanvasSize)
window.addEventListener('scroll', setCanvasSize)
}, [])
useEffect(() => {
canvasCtx.current.fillStyle = 'black';
canvasCtx.current.fillRect(0, 0, width, height);
cancelAnimationFrame(animation.current);
draw()
}, [width, height])
/**
* @description 創(chuàng)建01字符串
* @returns 01字符串
*/
function generateRandomString() {
const characters = '01';
const len = Math.floor(Math.random() * (60 - 45 + 1)) + 45;
let randomString = '';
for (let i = 0; i < len; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
randomString += characters[randomIndex];
}
return randomString;
}
/** 單條數(shù)字雨生成*/
const drawSeparateLine = useCallback((fontSize: number, width: number, x: number, y: number, text: string) => {
canvasCtx.current.font = `bold ${fontSize}px Arial`
let grd = canvasCtx.current.createLinearGradient(x, y, x + width, y);
grd.addColorStop(0, "aqua");
grd.addColorStop(1, "transparent");
canvasCtx.current.fillStyle = grd
canvasCtx.current.shadowColor = "aqua"; // 設置陰影顏色
canvasCtx.current.shadowBlur = 20; // 設置陰影的模糊程度
canvasCtx.current.fillText(text, x, y)
}, [])
/**
* @description 記錄數(shù)據(jù)
* @param x 起始坐標 {number}
* @param y 起始坐標 {number}
* @param text 文字 {string}
* @returns
*/
const recordData = useCallback((x: number, y: number, text: string) => {
const fontSize = createRandomNum(17, 24)
const speed = createRandomNum(2, 4)
let textOne = canvasCtx.current.measureText(text);
drawSeparateLine(fontSize, textOne.width, x, y, text)
dataList.current.push({
x,
y,
text,
fontSize,
width: textOne.width,
speed
})
}, [])
/** 畫整個頁面*/
const draw = useCallback(() => {
canvasCtx.current.clearRect(0, 0, width, height)
canvasCtx.current.fillStyle = 'black';
canvasCtx.current.fillRect(0, 0, width, height);
/** 移動*/
for (let i = 0; i < dataList.current.length; i++) {
let item = dataList.current[i];
drawSeparateLine(item.fontSize, item.width, item.x - item.speed, item.y, item.text)
dataList.current[i] = {
...dataList.current[i],
x: item.x - item.speed
}
}
/** 增加新的*/
const maxWidth = window.innerWidth * 3;
const minWidth = window.innerWidth;
const maxHeight = window.innerHeight;
const minHeight = 0;
for (let i = 0; i < amount.current - dataList.current.length; i++) {
let x = createRandomNum(minWidth, maxWidth)
let y = createRandomNum(minHeight, maxHeight)
recordData(x, y, generateRandomString())
}
/** 去除舊的*/
let list: number[] = [];
for (let i = 0; i < dataList.current.length; i++) {
if (dataList.current[i].x + dataList.current[i].width <= 0) {
list.push(i)
}
}
dataList.current = dataList.current.filter((item, ind) => {
return !list.includes(ind)
})
animation.current = requestAnimationFrame(draw);
}, [width, height])
return (
<>
<div className='rain'>
<canvas ref={canvasDom}
width={width}
height={height}
></canvas>
</div>
</>
)
}放大鏡
/** 放大鏡*/
import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import img1 from '../assets/imgs/1.jpg'
import './MagnifyingGlass.scss'
export default function Canvas() {
const frame = useRef<any>(null)
const canvasDom = useRef<any>(null)
const magnifyCanvasDom = useRef<any>(null)
const canvasCtx = useRef<any>(null)
const magnifyCanvasCtx = useRef<any>(null)
const magnifyingGlassSize = useRef(40)
const [top, setTop] = useState(0);
const [left, setLeft] = useState(0);
const initLocation = useRef<any>({
x: 0,
y: 0,
minX: 0,
maxX: 0,
minY: 0,
maxY: 0,
size: 0,
})
const setInitPointer = useCallback(() => {
let info = canvasDom.current.getBoundingClientRect()
initLocation.current = {
x: info.x,
y: info.y,
minX: info.x,
maxX: info.x + info.width - magnifyingGlassSize.current,
minY: info.y,
maxY: info.y + info.height - magnifyingGlassSize.current,
}
}, [])
/** 初始化,渲染圖片*/
useEffect(() => {
if (canvasDom.current == null) {
return
}
canvasCtx.current = (canvasDom.current).getContext('2d');
magnifyCanvasCtx.current = (magnifyCanvasDom.current).getContext('2d');
setInitPointer()
let img = new Image();
img.src = img1;
img.onload = () => {
const canvasWidth = canvasDom.current.width;
const canvasHeight = canvasDom.current.height;
const imageWidth = img.width;
const imageHeight = img.height;
const scale = Math.min(canvasWidth / imageWidth, canvasHeight / imageHeight);
const scaledWidth = imageWidth * scale;
const scaledHeight = imageHeight * scale;
canvasCtx.current.drawImage(img, 0, 0, scaledWidth, scaledHeight)
magnifyCanvasCtx.current.drawImage(
canvasDom.current,
0,
0,
magnifyingGlassSize.current,
magnifyingGlassSize.current,
0,
0,
300,
300
);
}
frame.current.addEventListener('mousemove', onMousemove)
window.addEventListener('resize', setInitPointer)
window.addEventListener('scroll', setInitPointer)
return () => {
frame.current.removeEventListener('mousemove', onMousemove)
window.removeEventListener('resize', setInitPointer)
window.removeEventListener('scroll', setInitPointer)
}
}, [])
const onMousemove = useCallback((e: MouseEvent) => {
let x = e.x;
let y = e.y;
let dataY = y - initLocation.current.y - magnifyingGlassSize.current / 2;
//判斷邊界
if (dataY < initLocation.current.minY) {
dataY = initLocation.current.minY
} else if (dataY > initLocation.current.maxY) {
dataY = initLocation.current.maxY
}
setTop(dataY)
//判斷邊界
let dataX = x - initLocation.current.x - magnifyingGlassSize.current / 2;
if (dataX < initLocation.current.minX) {
dataX = initLocation.current.minX
} else if (dataX > initLocation.current.maxX) {
dataX = initLocation.current.maxX
}
setLeft(dataX)
/** 切圖*/
magnifyCanvasCtx.current.drawImage(
canvasDom.current,
dataX,
dataY,
magnifyingGlassSize.current,
magnifyingGlassSize.current,
0, 0,
300, 300
);
}, [])
return (
<>
<div ref={frame} style={{
display: 'inline-block'
}}>
<canvas
className='glass'
ref={canvasDom} width={300} height={300}>
</canvas>
<div
style={{
position: 'fixed',
zIndex: 0,
top,
left,
width: `${magnifyingGlassSize.current}px`,
height: `${magnifyingGlassSize.current}px`,
background: 'yellow',
opacity: '.2'
}}>
</div>
</div>
<canvas
ref={magnifyCanvasDom} width={300} height={300}>
</canvas>
</>
)
}結(jié)語
分享結(jié)束
到此這篇關(guān)于Canvas實現(xiàn)數(shù)字雨和放大鏡效果的代碼示例的文章就介紹到這了,更多相關(guān)Canvas數(shù)字雨和放大鏡效果內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Bootstrap CSS組件之下拉菜單(dropdown)
這篇文章主要為大家詳細介紹了Bootstrap CSS組件之下拉菜單(dropdown),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12
echarts圖表無數(shù)據(jù)/空數(shù)據(jù)如何展示"暫無數(shù)據(jù)"
在開發(fā)echarts的時候我們不得不考慮數(shù)據(jù)為空的情況,其實有很多種解決辦法,下面這篇文章主要給大家介紹了關(guān)于echarts圖表無數(shù)據(jù)/空數(shù)據(jù)如何展示“暫無數(shù)據(jù)”的相關(guān)資料,需要的朋友可以參考下2022-10-10
JavaScript trim 實現(xiàn)去除字符串首尾指定字符的簡單方法
下面小編就為大家?guī)硪黄狫avaScript trim 實現(xiàn)去除字符串首尾指定字符的簡單方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12
性能優(yōu)化篇之Webpack構(gòu)建速度優(yōu)化的建議
這篇文章主要介紹了性能優(yōu)化篇之Webpack構(gòu)建速度優(yōu)化的建議,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-04-04
使用Webpack構(gòu)建多頁面程序的實現(xiàn)步驟
這篇文章主要介紹了使用Webpack構(gòu)建多頁面程序的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03
javascript使用輸出語句實現(xiàn)網(wǎng)頁特效代碼
這篇文章主要介紹javascript使用輸出語句實現(xiàn)網(wǎng)頁特效,有需要的朋友可以參考下2015-08-08
一文詳解JSON.parse和JSON.stringify的用法
Json.stringify()和toString()兩者雖然都可以講目標值轉(zhuǎn)為字符串,但是還是有本質(zhì)區(qū)別的,下面這篇文章主要給大家介紹了關(guān)于JSON.parse和JSON.stringify用法的相關(guān)資料,需要的朋友可以參考下2023-01-01

