canvas绘制时钟 光明 | 黑暗主题

2021-12-09 15:47:09 浏览数 (1)

先上效果图

canvas时钟的绘制参考了 # Sunshine_Lin的# 为了让她10分钟入门canvas,我熬夜写了3个小项目和这篇文章

我个人认为比较有难点的就是四个阴影的绘制

代码语言:javascript复制
// 外右下阴影
ctx.shadowOffsetX = 10; // 阴影Y轴偏移
ctx.shadowOffsetY = 10; // 阴影X轴偏移
ctx.shadowBlur = 30;    // 模糊尺寸
ctx.shadowColor = theme === 'night'?'rgba(36, 37, 46, 0.1)':'rgba(0, 0, 0, 0.1)'; // 颜色
ctx.fillStyle = theme === 'night'?'#24252e':'#ebecf3';     // 填充路径的当前的颜色
ctx.arc(0, 0, 250, 0, 2 * Math.PI);
ctx.fill();             // 关闭该路径,然后填充该路径

// 外左上阴影
ctx.shadowOffsetX = -10; // 阴影Y轴偏移
ctx.shadowOffsetY = -10; // 阴影X轴偏移
ctx.shadowBlur = 30;    // 模糊尺寸
ctx.shadowColor = theme === 'night'?'#3d3d3f':'white'; // 颜色
ctx.arc(0, 0, 250, 0, 2 * Math.PI);
ctx.fill();             // 关闭该路径,然后填充该路径

//绘制内部阴影
arcInnerShadow(ctx, 0, 0, 250,theme === 'night'?'#4a4a4e':'white',-10,-10);
arcInnerShadow(ctx, 0, 0, 250,theme === 'night'?'rgba(0, 0, 0, 0.5)':'rgba(0, 0, 0, 0.2)',10,10);
// 【圆形内阴影(边框 阴影,再把边框和外阴影裁剪掉)】
// 参数说明:ctx上下文内容,x,y,r同arc的入参,shadowColor阴影颜色,OffsetX和OffsetY一同控制偏移角度。
function arcInnerShadow (ctx, x, y, r, shadowC, OffsetX, OffsetY) {
  let shadowColor = shadowC || 'white'; // 阴影颜色
  ctx.save();
  ctx.beginPath();
  ctx.shadowOffsetX = OffsetX; // 阴影Y轴偏移
  ctx.shadowOffsetY = OffsetY; // 阴影X轴偏移
  // 裁剪区(只保留内部阴影部分)
  ctx.arc(x, y, r, 0, 2*Math.PI);
  ctx.clip();

  // 边框 阴影
  ctx.beginPath();
  ctx.lineWidth = 20;
  ctx.shadowColor = shadowColor;
  ctx.shadowBlur = 30;
  // 因线是由坐标位置向两则画的,所以半径加上线宽的一半。同时又因为线有毛边,所以半径再多加1px,处理毛边。
  ctx.arc(x, y, r   20/2   1, 0, 2*Math.PI);
  ctx.stroke();
  // 取消阴影
  ctx.shadowBlur = 0;
  ctx.restore();
}
复制代码

好了,完整代码奉上,感兴趣的小伙伴可以自己试一试

HTML Css

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style type="text/css">
     *{
          margin: 0;
          padding: 0;
      }
     body{
          background-color: #ebecf3;
      }
    .canvas{
        text-align: center;
        position: relative;
        width: 600px;
        margin: 250px auto 0 auto;
    }
    .text{
          width: 55%;
          margin: 0 auto;
      }
    .time{
        font-size: 165px;
        color: #303133;
        text-align: center;
    }
    .APM{
        font-size: 35px;
        letter-spacing: 3px;
        text-align: right;
        margin-top: 50px;
    }
    .date{
        font-size: 35px;
        color: #303133;
        text-align: center;
    }
    .icon{
        box-shadow: 0 0 5px rgba(0,0,0,0.2) inset;
        display: table-cell;
        vertical-align:middle;
        text-align: center;
        width: 60px;
        height: 60px;
        line-height: 55px;
        border-radius: 50%;
        position: absolute;
        top: 0;
        right: 0;
    }
    .icon-img{
        width: 40px;
        height: 40px;
        vertical-align:middle;
    }
    .name{
        position: absolute;
        bottom: 90px;
        font-size: 30px;
        left: 45%;
    }
  </style>
</head>
<body>
  <div class="canvas">
    <canvas id="canvas" width="600" height="600"></canvas>
    <div class="icon" id="icon" onclick="clickIcon()">
      <img src="月亮.png" class="icon-img">
    </div>
  </div>
  <div class="text">
    <div class="APM t"></div>
    <div class="time t"></div>
    <div class="date t"></div>
  </div>
  <div class="name">I'm inline</div>
  <script src="canvas.js"></script>
</body>
</html>
复制代码

JS

代码语言:javascript复制
// 定义夜间 白天 背景色
const dayColor = '#ebecf3';
const nightColor = '#24252e';
let theme = '';   // 主题 day / night
// 图标点击
function clickIcon(){
  let img = document.getElementsByTagName('img');
  let imgName = img[0].outerHTML.slice(10,16);
  img[0].src = imgName === '月亮.png'?'太阳.png':'月亮.png';
  if (imgName === '月亮.png'){  // 夜间
    theme = 'night';
    document.body.style.backgroundColor = nightColor;
    const t = document.getElementsByClassName('t');
    for (let i = 0; i < t.length; i  ){
      document.getElementsByClassName('t')[i].style.color = dayColor;
    }
    document.getElementsByClassName('name')[0].style.color = dayColor;
  }else {
    theme = 'day';
    document.body.style.backgroundColor = dayColor;
    const t = document.getElementsByClassName('t');
    for (let i = 0; i < t.length; i  ){
      document.getElementsByClassName('t')[i].style.color = nightColor;
    }
    document.getElementsByClassName('name')[0].style.color = nightColor;
  }
  canvasMapping();
}

let c = document.getElementById("canvas");
let ctx = c.getContext('2d');
// 获取日期时间
timeDate();
// 绘图
canvasMapping();
// 获取日期时间
function timeDate (){
  let time = new Date();
  let hour = time.getHours() % 12;
  let min = time.getMinutes();
  let y = time.getFullYear();
  let m = time.getMonth();
  let d = time.getDate();
  hour = hour < 10 ? `0${hour}` : hour;
  min = min < 10 ? `0${min}` : min;
  let str = time.getHours() > 12 ? 'PM' : 'AM';

  let date = new Date(`${y}-${m}-${d}`.replace(/-/g,'/')); //Wed Jan 02 2019 00:00:00 GMT 0800 (China Standard Time)
  let chinaDate = date.toDateString(); //"Tue, 01 Jan 2019 16:00:00 GMT"
  //之后的处理是一样的
  let chinaDateArray = chinaDate.split(' '); //["Wed", "Jan", "02", "2019"]
  let displayDate = `${chinaDateArray[2]} ${chinaDateArray[1]} ${chinaDateArray[3]}`; //"Jan 02, 2019"

  let timeDate = document.getElementsByClassName('time');
  timeDate[0].innerHTML = `${hour}:${min}`;

  let APM = document.getElementsByClassName('APM');
  APM[0].innerHTML = str;

  let t = document.getElementsByClassName('date');
  t[0].innerHTML = displayDate;
}
// 定时器 一秒执行一次绘制
setInterval(()=>{ canvasMapping() },1000);
// canvas 绘图
function canvasMapping(){
  ctx.save()
  ctx.clearRect(0, 0, 600, 600);          // 清除画布
  ctx.translate(300, 300);                // 设置中心点,此时300,300变成了坐标的0,0
  ctx.save();                 // 保存当前的绘图状态

  // 画大圆
  ctx.strokeStyle = theme === 'night'?'#24252e':'#ebecf3';    // 设置线条颜色
  ctx.beginPath();                        // 丢弃当前路径,并且开始一条新路径
  ctx.arc(0, 0, 250, 0, 2 * Math.PI);     // 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
  ctx.stroke();                           // 执行画线段的操作stroke
  ctx.closePath();                        // 关闭一条打开的子路径

  // 外右下阴影
  ctx.shadowOffsetX = 10; // 阴影Y轴偏移
  ctx.shadowOffsetY = 10; // 阴影X轴偏移
  ctx.shadowBlur = 30;    // 模糊尺寸
  ctx.shadowColor = theme === 'night'?'rgba(36, 37, 46, 0.1)':'rgba(0, 0, 0, 0.1)'; // 颜色
  ctx.fillStyle = theme === 'night'?'#24252e':'#ebecf3';     // 填充路径的当前的颜色
  ctx.arc(0, 0, 250, 0, 2 * Math.PI);
  ctx.fill();             // 关闭该路径,然后填充该路径

  // 外左上阴影
  ctx.shadowOffsetX = -10; // 阴影Y轴偏移
  ctx.shadowOffsetY = -10; // 阴影X轴偏移
  ctx.shadowBlur = 30;    // 模糊尺寸
  ctx.shadowColor = theme === 'night'?'#3d3d3f':'white'; // 颜色
  ctx.arc(0, 0, 250, 0, 2 * Math.PI);
  ctx.fill();             // 关闭该路径,然后填充该路径

  //绘制内部阴影
  arcInnerShadow(ctx, 0, 0, 250,theme === 'night'?'#4a4a4e':'white',-10,-10);
  arcInnerShadow(ctx, 0, 0, 250,theme === 'night'?'rgba(0, 0, 0, 0.5)':'rgba(0, 0, 0, 0.2)',10,10);
  // 【圆形内阴影(边框 阴影,再把边框和外阴影裁剪掉)】
  // 参数说明:ctx上下文内容,x,y,r同arc的入参,shadowColor阴影颜色,OffsetX和OffsetY一同控制偏移角度。
  function arcInnerShadow (ctx, x, y, r, shadowC, OffsetX, OffsetY) {
    let shadowColor = shadowC || 'white'; // 阴影颜色
    ctx.save();
    ctx.beginPath();
    ctx.shadowOffsetX = OffsetX; // 阴影Y轴偏移
    ctx.shadowOffsetY = OffsetY; // 阴影X轴偏移
    // 裁剪区(只保留内部阴影部分)
    ctx.arc(x, y, r, 0, 2*Math.PI);
    ctx.clip();

    // 边框 阴影
    ctx.beginPath();
    ctx.lineWidth = 20;
    ctx.shadowColor = shadowColor;
    ctx.shadowBlur = 30;
    // 因线是由坐标位置向两则画的,所以半径加上线宽的一半。同时又因为线有毛边,所以半径再多加1px,处理毛边。
    ctx.arc(x, y, r   20/2   1, 0, 2*Math.PI);
    ctx.stroke();
    // 取消阴影
    ctx.shadowBlur = 0;
    ctx.restore();
  }

  // 获取时分秒
  let time = new Date();
  let hour = time.getHours() % 12;
  let min = time.getMinutes();
  let sec = time.getSeconds();
  if (sec === 0){
    timeDate()
  }

  // 时针  rotate(60);将**旋转60度
  ctx.rotate(2 * Math.PI / 12 * hour   2 * Math.PI / 12 * (min / 60) - Math.PI / 2);  // (2 * 180 / 12 * 小时   2 * 180 / 12 * (分钟 / 60)- 180 / 2);
  ctx.beginPath();
  ctx.moveTo(-10, 0); // moveTo设置画线起点
  ctx.lineTo(130, 0);  // lineTo设置画线经过点
  ctx.lineWidth = 8; // 设置线宽
  ctx.lineCap="round";
  ctx.strokeStyle = theme === 'night'?dayColor:nightColor;
  ctx.stroke();
  ctx.closePath();
  ctx.restore();      // 恢复成上一次save的状态
  ctx.save();         // 恢复完再保存一次

  // 分针
  ctx.rotate(2 * Math.PI / 60 * min   2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
  ctx.beginPath();
  ctx.moveTo(-10, 0);
  ctx.lineTo(180, 0);
  ctx.lineWidth = 8;
  ctx.lineCap="round";
  ctx.strokeStyle = theme === 'night'?dayColor:nightColor;
  ctx.stroke();
  ctx.closePath();
  ctx.restore();
  ctx.save();

  // 画小圆 外圈
  ctx.beginPath();
  ctx.arc(0, 0, 15, 0, 2*Math.PI);
  ctx.fillStyle = theme === 'night'?nightColor: '#fff' ;      // 设置填充色
  ctx.strokeStyle = theme === 'night'?nightColor: '#fff';    // 设置线条颜色
  ctx.fill();                 // 开始填充
  ctx.stroke();
  ctx.closePath();

  // 画小圆
  ctx.beginPath();
  ctx.arc(0, 0, 10  , 0, 2*Math.PI);
  ctx.fillStyle="blue";     // 设置填充色
  ctx.fill();               // 开始填充
  ctx.stroke();
  ctx.closePath();

  //秒针
  ctx.rotate(2 * Math.PI / 60 * sec - Math.PI / 2);
  ctx.beginPath();
  ctx.moveTo(-20, 0);
  ctx.lineTo(160, 0);
  ctx.lineWidth = 5; // 设置线宽
  ctx.lineCap="round";
  ctx.strokeStyle = 'blue';
  ctx.stroke();
  ctx.closePath();
  ctx.restore();
  ctx.save();

  // 绘制刻度,也是跟绘制时分秒针一样,只不过刻度是死的
  ctx.lineWidth = 5;
  for (let i = 0; i < 4; i  ) {
    ctx.rotate(2 * Math.PI / 4);
    ctx.beginPath();
    ctx.lineCap="round";
    ctx.strokeStyle = theme === 'night'?dayColor:nightColor;
    ctx.moveTo(230, 0);
    ctx.lineTo(200, 0);
    ctx.stroke();
    ctx.closePath();
  }

  ctx.restore();    // 恢复之前保存的绘图状态
  ctx.restore();
}
复制代码

0 人点赞