马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
思绪/实现步调
- 初始化点的信息(数目、初始坐标、移动方向、移动速率、巨细、颜色)
示例代码
- <template>
- <div class='random-dot-line-canvas'>
- <!-- <div style="margin-bottom: 20px;"><el-button @click="nextDraw">下一帧</el-button></div> -->
- <div class="config">
- <el-form :inline="true" :model="canvasConfig">
- <el-form-item label="画布宽度" prop="canvasWidth">
- <el-input-number v-model="canvasConfig.canvasWidth" :min="800" :max="1920" :step="50"></el-input-number>
- </el-form-item>
- <el-form-item label="画布高度" prop="canvasHeight">
- <el-input-number v-model="canvasConfig.canvasHeight" :min="200" :max="900" :step="50"></el-input-number>
- </el-form-item>
- <el-form-item label="点数量" prop="DOT_NUM">
- <el-input-number v-model="canvasConfig.DOT_NUM" :min="10" :max="200" :step="5"></el-input-number>
- </el-form-item>
- <el-form-item label="点半径" prop="DOT_RADIUS">
- <el-input-number v-model="canvasConfig.DOT_RADIUS" :min="1" :max="20" :step="1"></el-input-number>
- </el-form-item>
- <el-form-item label="点颜色" prop="DOT_COLOR">
- <el-color-picker v-model="canvasConfig.DOT_COLOR" />
- </el-form-item>
- <el-form-item label="线颜色" prop="LINE_COLOR">
- <el-color-picker v-model="canvasConfig.LINE_COLOR" />
- </el-form-item>
- <el-form-item label="连线最大距离" prop="DOT_DRAW_LINE_MAX_DIST">
- <el-input-number v-model="canvasConfig.DOT_DRAW_LINE_MAX_DIST" :min="50" :max="400" :step="20"></el-input-number>
- </el-form-item>
- <el-form-item label="点移动速度" prop="DOT_MOVE_SPEED">
- <el-input-number v-model="canvasConfig.DOT_MOVE_SPEED" :min="1" :max="20" :step="1"></el-input-number>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="redraw">重绘</el-button>
- <el-button type="primary" @click="reset">重置</el-button>
- </el-form-item>
- </el-form>
- </div>
- <canvas
- v-if="!initing"
- id="random-dot-line-canvas"
- :width="canvasWidth"
- :height="canvasHeight"
- style="background-color: black;"
- color-space="srgb"
- >
- Your browser does not support the HTML5 canvas tag.
- </canvas>
- </div>
- </template>
- <script setup name='random-dot-line-canvas'>
- import { ref, onMounted, nextTick, watch } from 'vue'
- import store from '../../../../store';
- const themeColor = ref(store.state.setting.themeColor || '#ffffff') // 主题色
- const ctx = ref()
- const initing = ref(true)
- const canvasWidth = ref(800) // 画布宽度
- const canvasHeight = ref(400) // 画布高度
- const dotList = ref([]) // 点的坐标、移动方向/移动速度
- const DOT_NUM = ref(40) // 点的数量
- const DOT_RADIUS = ref(2.5) // 点的半径
- const DOT_COLOR = ref(themeColor) // 点的颜色
- const LINE_COLOR = ref(themeColor) // 连线颜色
- const DOT_DRAW_LINE_MAX_DIST = ref(100) // 两点之间要连线的最大距离
- const DOT_MOVE_SPEED = ref(1) // 点移动速度
- const canvasConfig = ref({ // 表单配置项
- canvasWidth: canvasWidth.value,
- canvasHeight: canvasHeight.value,
- DOT_NUM: DOT_NUM.value,
- DOT_RADIUS: DOT_RADIUS.value,
- DOT_COLOR: DOT_COLOR.value,
- LINE_COLOR: LINE_COLOR.value,
- DOT_DRAW_LINE_MAX_DIST: DOT_DRAW_LINE_MAX_DIST.value,
- DOT_MOVE_SPEED: DOT_MOVE_SPEED.value,
- })
- const animationNum = ref(null)
- const animationMouseNum = ref(null)
- const canvasPositionInPage = ref() // 画布位置
- const mousePosition = ref({}) // 鼠标位置
- onMounted(()=>{
- init()
- })
- watch(
- () => mousePosition.value,
- (newConfig, oldConfig = {}) => {
- if(newConfig.inCanvas && !oldConfig.inCanvas) {
- // console.log('移入画布')
- mouseAdsorb()
- } else if (newConfig.inCanvas === false) {
- // console.log('移出画布')
- cancelAnimationFrame(animationMouseNum.value)
- }
- },
- { immediate: true, deep: true }
- )
- /** 初始化函数 */
- const init = async() => {
- initing.value = false
- await nextTick()
- let canvas = document.getElementById('random-dot-line-canvas')
- canvasPositionInPage.value = canvas.getBoundingClientRect()
- // {"x": 40, "y": 183, "width": 800, "height": 400, "top": 183, "right": 840, "bottom": 583, "left": 40 }
- try {
- ctx.value = canvas.getContext('2d')
- } catch (error) {
- console.log('canvas,ctx,getContext', error)
- }
- createdDot() // 创建点坐标
- draw() // 绘制
- mouseMove() // 鼠标移动相关逻辑
- dotMove() // 点动起来
- }
- /** 重绘 */
- const redraw = () => {
- cancelAnimationFrame(animationNum.value)
- initing.value = true
- setNewConfigValue(canvasConfig.value)
- init()
- }
- /** 重置 */
- const reset = () => {
- initing.value = true
- setNewConfigValue({
- canvasWidth: 800,
- canvasHeight: 400,
- DOT_NUM: 40,
- DOT_RADIUS: 2.5,
- DOT_DRAW_LINE_MAX_DIST: 100,
- DOT_MOVE_SPEED: 1,
- })
- init()
- }
- /** 设置配置项值 */
- const setNewConfigValue = (newConfig) => {
- canvasWidth.value = newConfig.canvasWidth || canvasWidth.value
- canvasHeight.value = newConfig.canvasHeight || canvasHeight.value
- DOT_NUM.value = newConfig.DOT_NUM || DOT_NUM.value
- DOT_RADIUS.value = newConfig.DOT_RADIUS || DOT_RADIUS.value
- DOT_COLOR.value = newConfig.DOT_COLOR || DOT_COLOR.value
- LINE_COLOR.value = newConfig.LINE_COLOR || LINE_COLOR.value
- DOT_DRAW_LINE_MAX_DIST.value = newConfig.DOT_DRAW_LINE_MAX_DIST || DOT_DRAW_LINE_MAX_DIST.value
- DOT_MOVE_SPEED.value = newConfig.DOT_MOVE_SPEED || DOT_MOVE_SPEED.value
- }
- /** 绘制函数 */
- const draw = () => {
- ctx.value.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
- drawDot() // 绘制点
- drawLine() // 连线
- if(mousePosition.value.inCanvas) { // 鼠标在画布内 执行吸附逻辑
- drawLineByMouse() // 连线到鼠标
- }
- }
- /** 下一帧-调试用 */
- const nextDraw = () => {
- dotMove()
- }
- /** 创建点坐标 */
- const createdDot =() => {
- dotList.value = []
- for (let num = 1; num <= DOT_NUM.value; num++) {
- const x = getRandomInteger(0, canvasWidth.value)
- const y = getRandomInteger(0, canvasHeight.value)
- const move_x = (+(Math.random() * 2 - 1).toFixed(2)) * DOT_MOVE_SPEED.value // 移动x轴的速度
- const move_y = (+(Math.random() * 2 - 1).toFixed(2)) * DOT_MOVE_SPEED.value // 移动y轴的速度
- dotList.value.push({x,y,move_x,move_y})
- }
- }
- /** 绘制点 */
- const drawDot = () => {
- ctx.value.save();
- ctx.value.translate(0.5, 0.5);
- dotList.value.forEach((dot, index)=>{
- ctx.value.fillStyle = index === 0 ? 'red' : DOT_COLOR.value || '#fff'
- ctx.value.beginPath();
- ctx.value.arc(dot.x, dot.y, DOT_RADIUS.value, 0, 2*Math.PI)
- ctx.value.fill()
- ctx.value.closePath();
- })
- ctx.value.restore();
- }
- /** 点之间连线 */
- const drawLine = () => {
- ctx.value.save();
- ctx.value.translate(0.5, 0.5);
- const lineColorRgb = hexToRgb(LINE_COLOR.value)
- dotList.value.forEach((dot1, index1)=>{
- dotList.value.forEach((dot2, index2)=>{
- const s = getDistanceByTwoDot(dot1, dot2) // 两点之间的距离
- if(index1 !== index2 && s <= DOT_DRAW_LINE_MAX_DIST.value) {
- ctx.value.lineWidth = 1
- ctx.value.strokeStyle = `rgba(${lineColorRgb.r}, ${lineColorRgb.g}, ${lineColorRgb.b}, ${((DOT_DRAW_LINE_MAX_DIST.value-s) / DOT_DRAW_LINE_MAX_DIST.value)})`
- ctx.value.beginPath();
- ctx.value.moveTo(dot1.x, dot1.y)
- ctx.value.lineTo(dot2.x, dot2.y)
- ctx.value.stroke()
- ctx.value.closePath();
- }
- })
- })
- ctx.value.restore();
- }
- /** 点有规律的动起来 */
- const dotMove = () => {
- dotList.value.forEach((dot, index) => {
- let nextX = dot.x
- let nextY = dot.y
- nextX = dot.x + dot.move_x
- nextY = dot.y + dot.move_y
- if(nextX > canvasWidth.value - DOT_RADIUS.value) {
- nextX = canvasWidth.value - DOT_RADIUS.value
- dot.move_x = -dot.move_x
- } else if(nextX < DOT_RADIUS.value) {
- nextX = DOT_RADIUS.value
- dot.move_x = -dot.move_x
- }
- if(nextY > canvasHeight.value - DOT_RADIUS.value) {
- nextY = canvasHeight.value - DOT_RADIUS.value
- dot.move_y = -dot.move_y
- } else if(nextY < DOT_RADIUS.value) {
- nextY = DOT_RADIUS.value
- dot.move_y = -dot.move_y
- }
- dot.x = nextX
- dot.y = nextY
- })
- draw() // 绘制
- animationNum.value = requestAnimationFrame(dotMove)
- }
- /** 鼠标移动相关逻辑 */
- const mouseMove = () => {
- const canvas = document.getElementById('random-dot-line-canvas')
- canvas.addEventListener('mousemove', (e) => {
- mousePosition.value = {
- inCanvas: true,
- x: e.offsetX,
- y: e.offsetY,
- offsetX: e.offsetX, // 相对于canvas
- offsetY: e.offsetY,
- pageX: e.pageX, // 相对于页面
- pageY: e.pageY,
- }
- })
- canvas.addEventListener('mouseleave', (e) => {
- mousePosition.value.inCanvas = false
- })
- }
- /** 点移动兼容鼠标位置的戏份逻辑 */
- const mouseAdsorb = () => {
- dotList.value.forEach((dot) => {
- let nextX = dot.x
- let nextY = dot.y
- const attractiveRange = DOT_DRAW_LINE_MAX_DIST.value + 50 // 吸引力作用范围
- const adsorptionDistance = DOT_DRAW_LINE_MAX_DIST.value // 吸附距离
- const distance = getDistanceByTwoDot(mousePosition.value, dot) // 鼠标和点的距离
- if(distance < attractiveRange && distance > adsorptionDistance) {
- dot.isAttractive = true // 点正在被鼠标吸引
- const { mouse_move_x, mouse_move_y } = getAdsorbSpeed(dot, mousePosition.value) // 计算鼠标吸附点xy方向上的速度
- nextX += mouse_move_x
- nextY += mouse_move_y
- } else if(distance <= adsorptionDistance) { // 吸附点
- dot.isAdsorption = true // 点已经被吸附
- dot.isAttractive = false
- } else {
- dot.isAttractive = false // 点没有被鼠标吸引
- dot.isAdsorption = false // 点没有被吸附
- }
- dot.x = nextX
- dot.y = nextY
- })
- draw() // 绘制
- animationMouseNum.value = requestAnimationFrame(mouseAdsorb)
- }
- /** 点鼠标之间连线 */
- const drawLineByMouse = () => {
- ctx.value.save();
- ctx.value.translate(0.5, 0.5);
- const lineColorRgb = hexToRgb(LINE_COLOR.value)
- dotList.value.forEach((dot)=>{
- if(dot.isAdsorption || dot.isAttractive) { // 被吸引 或 被吸附的点 与鼠标位置连线
- ctx.value.lineWidth = 1
- ctx.value.strokeStyle = `rgba(${lineColorRgb.r}, ${lineColorRgb.g}, ${lineColorRgb.b}, ${0.3})`
- ctx.value.beginPath();
- ctx.value.moveTo(dot.x, dot.y)
- ctx.value.lineTo(mousePosition.value.x, mousePosition.value.y)
- ctx.value.stroke()
- ctx.value.closePath();
- }
- })
- ctx.value.restore();
- }
- /** 工具函数 */
- // 生成min-max范围内随机整数
- const getRandomInteger = (min, max) => {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
- // 计算两点之间的距离
- const getDistanceByTwoDot = (dot1, dot2) => {
- const w = Math.abs(dot1.x - dot2.x)
- const h = Math.abs(dot1.y - dot2.y)
- const s = Math.sqrt(w**2 + h**2)
- return s
- }
- // 十六进制颜色 转 rgb
- function hexToRgb(hex) {
- if(hex.includes('#')) {
- hex = hex.replace('#', '')
- }
- // 解析红、绿、蓝分量
- let r = parseInt(hex.substring(0, 2), 16);
- let g = parseInt(hex.substring(2, 4), 16);
- let b = parseInt(hex.substring(4, 6), 16) || 0;
- return { r, g, b };
- }
- // 计算点被吸引时 x y 方向上的移动速度
- const getAdsorbSpeed = (dot, mouse) => {
- // const distance = getDistanceByTwoDot(dot, mouse) // 鼠标和点的距离
- const distanceX = mouse.x - dot.x
- const distanceY = mouse.y - dot.y
- const speed = DOT_MOVE_SPEED.value * 3
- const mouse_move_x = +(speed * (distanceX < 0 ? -1 : 1)).toFixed(2)
- const mouse_move_y = +(speed * (distanceY < 0 ? -1 : 1) * Math.abs((distanceY / distanceX))).toFixed(2)
- return { mouse_move_x, mouse_move_y }
- }
- </script>
- <style lang='scss' scoped>
- #random-dot-line-canvas {
- cursor: pointer;
- }
- </style>
复制代码 结果
|