肥仔教程网

SEO 优化与 Web 开发技术学习分享平台

用canvas实现一个头像上的间断式的能量条

今天遇到一个很有意思的面试题,面试官给我一道题目,要我实现之前它们公司之前写的一个组件。

首先我介绍下这道题,首先我是先想到用 flex 布局来写头像分布,因为 grid 布局不能实现头像最后一排不能居中的效果。

然后这道题的 重点 来了,我一开始以为它头像上的边框是死的,是张贴图。然后我去问面试官,他说是一个能量条,能根据投票的数量进行改变。我脑袋有点懵,问 ai 也没结果,生成的非常垃圾,然后就开始思考怎么才能实现。首先想到的是 echats,但没有找到合适的,我就开始想 echats 是用 canvas 写的,我就想用 canvas 写下,在 bilibili 上看了下 canvas 的使用方法,于是就想到了这道题的解法。这是我的成果。

我就不做过多的讲解关于 canvas 的使用方法,我只在我的演示代码注释中讲每条代码的作用,和使用方法。不会的话,可以去看看 bilibili,然后做个笔记,然后就印象深刻了。

代码讲解 #技术分享

这里是初步实现的代码,写出了大概的轮廓方便理解。完整代码在最后面。

具体的代码讲解就写在注释中了。

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Document</title>

</head>

<body>

<canvas id="canvas" width="600" height="600" backgroud></canvas>

<script>

function animate() { var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d');

ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(-Math.PI / 2);

ctx.strokeStyle = 'rgb(144, 211, 205)'; ctx.lineWidth = 20; ctx.lineCap = "butt";

for (let i = 0; i < 17; i++) { ctx.beginPath(); ctx.arc(0, 0, 100, -Math.PI / 34, Math.PI / 34, false); ctx.stroke(); ctx.rotate(Math.PI / 16); ctx.closePath() } } animate(); </script>

</body>

</html>

成品代码

最后的成品我是用 vue 写的,没有特别去封装,毕竟只是面试题。

<template>

<div class="grid-container">

<div class="member-card" v-for="(member, index) in members" :key="index">

<canvas :id="'canvas-' + index" width="150" height="150"></canvas>

<div class="circle">

<img :src="member.avatar" alt="avatar" class="avatar" />

</div>

</div>

</div>

</template>

<script setup>

import { onMounted } from 'vue';

const members = [ { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 10 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 2 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 18 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 20 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 1 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 20 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 20 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 20 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 20 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 1 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 31 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 1 }, { name: '用户 A', avatar: 'https://img0.baidu.com/it/u=600722015,3838115472&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750', numbers: 1 }, ];

onMounted(() => { members.forEach((member, index) => { drawEnergyBar(index, member.numbers); }); });

function drawEnergyBar(index, count) { const canvas = document.getElementById(`canvas-${index}`); const ctx = canvas.getContext('2d');

ctx.clearRect(0, 0, canvas.width, canvas.height);

ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(-Math.PI / 2);

ctx.strokeStyle = 'rgb(144, 211, 205)'; ctx.lineWidth = 60; ctx.lineCap = "butt";

for (let i = 0; i < count; i++) { ctx.beginPath(); ctx.arc(0, 0, 44, -Math.PI / 36, Math.PI / 36, false); ctx.stroke(); ctx.rotate(Math.PI / 16); } } </script>

<style scoped>

canvas { position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 1; }

.member-card { position: relative; width: 150px; height: 150px; display: flex; justify-content: center; align-items: center; transition: transform 0.3s ease; border: rgb(144, 211, 205) solid 2px; border-radius: 50%; background-color: black; overflow: hidden; }

.circle { position: relative; border: 2px solid black; width: 100px; height: 100px; border-radius: 50%; overflow: hidden; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); z-index: 2; margin: 0; }

.grid-container { height: 100%; width: 100%; display: flex; flex-wrap: wrap; justify-content: center; gap: 30px; padding: 30px; max-width: calc(150px * 6 + 30px * 5); margin: 0 auto; background: url(https://pic.nximg.cn/file/20230303/33857552_140701783106_2.jpg); background-size: cover; background-position: center; background-repeat: no-repeat; background-attachment: fixed; }

.member-card { position: relative; width: 150px; display: flex; justify-content: center; align-items: center; transition: transform 0.3s ease; border: rgb(144, 211, 205) solid 2px; border-radius: 50%; background-color: black; }

.circle { position: relative; border: 2px solid black; margin: 20px 20px; width: 100px; height: 100px; border-radius: 50%; overflow: hidden; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }

.avatar { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease; } </style>

结语

虽然这道题有点难,但好处是我对 canvas 的理解加深了,canvas 绝对是前端的一个非常有用的东西,值得掘友们认真学习。原本这道题的灵感来源于 bilibili 上讲的 canvas 实现钟表中刻度的实现,虽然没用它的方法,因为他的方法会导致刻度变形,不是扇形的能量条,但是它旋转坐标轴的想法让我大受启发。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言