当今的云计算技术已经越来越成熟,基于云计算技术进行云端开发已经成为最新趋势。而 Cloud Studio 是一个基于云计算的 Web 端开发微服务平台,提供了代码编辑器、调试器、代码库,以及自动构建和部署工具等各种功能,帮助开发者在云端开发应用程序。
虽然我很早就接触了云端 IDE,可是能让我感到如此丝滑的开发体验,应该要属这一次!
在众多 IDE 中, Cloud Studio 绝对是你可以选择的一款。因为选择一款云端 IDE 不仅仅要看它的用户体验,还要看它能提供的资源。 Cloud Studio 背靠腾讯云,拥有丰富的云端资源可供开发者调用,这就等于你开了一家五金店,你在装修自家房子的时候,可以及时地拿到想要的工具。
而这一次为什么让我体验到了丝滑的感觉呢?因为我在尝试通过前端技术创建一个 Web 时钟动画的时候,发现 Cloud Studio 尽能如此快速、零顿挫感地帮我实现项目应用的预览、代码保存、代码托管等等一系列功能,丝毫没有感觉到浏览器的后台处理进程在“嚣张”地运行。那么,接下来废话不多说,我就来分享一下我是如何在 Cloud Studio 上快速创建 Web 时钟动画的。
本项目来源于《腾讯云 Cloud Studio 实战训练营》的参赛作品,该作品在腾讯云 Cloud Studio 中运行无误。
首先,需要创建你的账号。
由于 CODING 和 Cloud Studio 实现了账号互通,如果你有 CODING 的账号,你可以用 CODING 账号登录,完成账号授权。除此之外,你还可以通过 微信 或者 GitHub 进行登录。
登录之后,你会在 Cloud Studio 左侧的导航栏中看到 “空间模板”这么一个菜单,点击这个菜单,右侧会出现很多常用模板、框架模板供你选择。当然,你也可以选择右上角,点击 “新建模板” 按钮进行手动创建一个模板。
于是乎,为了特别一点,本项目就选择“新建模板”的方式进行创建:
只需要填写上述必要的信息即可。
进入我们刚刚创建的模板之后,会为你加载一个工作空间(注意,工作模式下,只能有一个工作空间在运行。ps:这点感觉不太友好)。
如果你是一个前端开发者,看到打开的工作空间画面是不是觉得很熟悉。没错!这是一个 VSCode 的编辑器界面。那么,这对于一个以使用 VSCode 作为日常开发主要编辑器的开发者来说,这样的工作空间可以说是零门槛上手。
接着,我们创建两个文件夹和三个文件,项目结构如下所示:
要开始创建一个时钟,你还需要具备一些基础知识。例如,我们下面先来学习一下 Web Animations API
。
Web Animations API 引入了时间线的概念。 默认情况下,所有动画都与文档的时间轴相关联。 这意味着动画共享相同的“内部时钟”——即从页面加载开始的时钟。
共享时钟使我们能够协调动画。无论是某种节奏还是一种模式,你都不必担心某些事情会延迟或超前发生。其中,它还有一个 startTime
属性是我们需要知道的。
要使动画在某个时刻开始,请使用 startTime
属性。 startTime
的值以页面加载后的毫秒数为单位。 开始时间设置为 1000.5
的动画将在文档时间轴的 currentTime
属性等于 1000.5
时开始播放。
你是否注意到开始时间值中的小数点了吗? 是的,你可以使用毫秒的分数来精确时间。 但是,精确度取决于浏览器设置。
另一个有趣的事情是开始时间也可以是负数。 你可以自由地将其设置为未来的某个时刻或过去的某个时刻。 将该值设置为 -1000
,你的动画状态就像页面加载时已经播放了一秒钟一样。 对于用户来说,动画似乎在他们甚至还没有考虑访问你的页面之前就已经开始播放了。
了解了上面的基础知识,下面我们就一起来看下如何使用 Web Animations API 来实现Web 时钟动画。
首先,打开 index.html
,在该文件中填入如下代码。这段代码主要用于创建时钟的布局:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>时钟动画title>
<link rel="stylesheet" href="/css/index.css">
head>
<body>
<template id="tick">
<div class="tick"><span>span>div>
template>
<template id="digit"><span class="digit" style="--len: 10;"><span>span>span>template>
<div id="analog-clock">
<div class="hour-ticks">div>
<div class="minute-ticks">div>
<div class="day">div>
<div class="hand second"><div class="shadow">div><div class="body">div>div>
<div class="hand minute"><div class="shadow">div><div class="body">div>div>
<div class="hand hour"><div class="shadow">div><div class="body">div>div>
<div class="dot">div>
div>
<div id="digital-clock">
<span class="hours">span><span>:span><span class="minutes">span><span>:span><span class="seconds">span><span>.span><span class="milliseconds">span>
div>
<script src="./js/index.js">script>
body>
html>
接着,还需要对布局进行样式上的处理。打开 css/index.css
文件,填入如下代码:
:root {
--face-size: 15rem;
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: sans-serif;
}
body > * {
margin: 1rem;
}
#analog-clock {
width: var(--face-size);
height: var(--face-size);
position: relative;
border: 3px solid #555;
border-radius: 50%;
font-weight: 400;
}
.dot {
--size: 9px;
position: absolute;
left: calc(50% - calc(var(--size) / 2));
top: calc(50% - calc(var(--size) / 2));
width: var(--size);
height: var(--size);
background-color: #333;
border-radius: 50%;
filter: drop-shadow(1px 1px 1px #333);
}
.hand {
position: absolute;
bottom: 50%;
left: calc(50% - calc(var(--width) / 2));
width: var(--width);
transform-origin: center bottom;
}
.hand > * {
position: absolute;
height: 100%;
width: 100%;
border-radius: 4px;
}
.hand .body {
background-color: #333;
}
.hand .shadow {
background-color: black;
opacity: 0.2;
filter: drop-shadow(0 0 1px black);
}
.second {
--width: 1px;
height: 50%;
transform-origin: center 80%;
margin-bottom: calc(var(--face-size) * -0.1)
}
.second .body {
background-color: black;
}
.minute {
--width: 3px;
height: 35%;
}
.hour {
--width: 5px;
height: 25%;
}
.day {
--size: 2ch;
position: absolute;
left: calc(50% - calc(var(--size) / 2));
top: calc(50% - calc(var(--size) / 2));
width: var(--size);
height: var(--size);
transform: translate(calc(var(--face-size) * 0.2));
}
.tick {
--width: 2px;
--height: 29px;
--shift: translateY(calc(var(--face-size) / -2));
position: absolute;
width: var(--width);
height: var(--height);
background-color: #666;
top: 50%;
left: calc(50% - calc(var(--width) / 2));
transform-origin: top center;
}
.tick > span {
--width: calc(calc(var(--face-size) * 3.141592653589793) / 24);
position: absolute;
width: var(--width);
top: 3px;
left: calc(var(--width) / -2);
text-align: center;
}
.hour-ticks .tick:nth-child(even) > span {
display: none;
}
.hour-ticks .tick:nth-child(odd) {
background: none;
}
.hour-ticks .tick {
transform: rotate(calc(var(--index) * 15deg)) var(--shift);
}
.minute-ticks .tick {
--width: 1px;
--height: 5px;
--shift: translateY(calc(var(--face-size) / -2.5));
background-color: black;
transform: rotate(calc(var(--index) * 6deg)) var(--shift);
}
.minute-ticks .tick:nth-child(5n+1) {
display: none;
}
#digital-clock {
font-size: 1.5rem;
line-height: 1;
}
#digital-clock > span {
display: inline-block;
vertical-align: top;
}
.digit {
display: inline-block;
overflow: hidden;
max-width: 1ch;
}
.digit.wide {
max-width: 2ch;
}
.digit > span {
display: inline-flex;
align-items: flex-start;
}
.digit.wide > span > span {
min-width: 2ch;
text-align: right;
}
.day .digit > span > span {
text-align: center;
}
此时,我们的时钟就画出来了,点击编辑器右上角的按钮,进行预览:
但是要让时钟走起来,还需要脚本的处理。就是我们前面提到的 Web Animation API。
接着,我们打开 js/index.js
文件,补充如下代码:
const ms = 1;
const s = ms * 1000;
const m = s * 60;
const h = m * 60;
const d = h * 24;
const start_time = (function () {
const time = new Date();
const document_time = document.timeline.currentTime;
const hour_diff = time.getHours() - time.getUTCHours();
const current_time = (Number(time) % d) + (hour_diff * h);
return document_time - current_time;
}());
const single_digit_keyframes = [
{transform: "translateX(0)"},
{transform: "translateX(calc(var(--len, 10) * -1ch)"}
];
const double_digit_keyframes = [
{transform: "translateX(0)"},
{transform: "translateX(calc(var(--len) * -2ch)"}
];
function range(len) {
return new Array(len).fill(true);
}
function digits(len = 10, zero_based = true) {
const digit = document.getElementById("digit").content.cloneNode(true);
digit.firstElementChild.style.setProperty("--len", len);
digit.firstElementChild.firstElementChild.append(
...range(len).map(function (ignore, index) {
const span = document.createElement("span");
span.textContent = zero_based ? index : index + 1;
return span;
})
);
if (len > 10) {
digit.firstElementChild.classList.add("wide");
}
return digit;
}
(function build_analog_clock() {
const clock = document.getElementById("analog-clock");
const tick_template = document.getElementById("tick");
const hour_marks_container = clock.querySelector(".hour-ticks");
const minute_marks_container = clock.querySelector(".minute-ticks");
const day = clock.querySelector(".day");
hour_marks_container.append(...range(24).map(function (ignore, index) {
const tick = tick_template.content.cloneNode(true);
const shifted = index + 1;
tick.firstElementChild.style.setProperty("--index", shifted);
tick.firstElementChild.firstElementChild.textContent = shifted;
return tick;
}));
minute_marks_container.append(...range(60).map(function (ignore, index) {
const tick = tick_template.content.cloneNode(true);
tick.firstElementChild.style.setProperty("--index", index);
tick.firstElementChild.firstElementChild.remove();
return tick;
}));
}());
(function build_digital_clock() {
const clock = document.getElementById("digital-clock");
const hours = clock.querySelector(".hours");
const minutes = clock.querySelector(".minutes");
const seconds = clock.querySelector(".seconds");
const milliseconds = clock.querySelector(".milliseconds");
hours.append(digits(24));
minutes.append(digits(6), digits());
seconds.append(digits(6), digits());
milliseconds.append(digits(), digits(), digits());
}());
(function start_analog_clock() {
const clock = document.getElementById("analog-clock");
if (clock === null) {
return;
}
const second = clock.querySelector(".second");
const minute = clock.querySelector(".minute");
const hour = clock.querySelector(".hour");
const hands = [second, minute, hour];
const hand_durations = [m, h, d];
const steps = [60, 60, 120];
const movement = [];
hands.forEach(function (hand, index) {
const duration = hand_durations[index];
const easing = `steps(${steps[index]}, end)`;
movement.push(hand.animate(
[
{transform: "rotate(0turn)"},
{transform: "rotate(1turn)"}
],
{duration, iterations: Infinity, easing}
));
const shadow = hand.querySelector(".shadow");
if (shadow) {
movement.push(shadow.animate(
[
{transform: "rotate(1turn) translate(3px) rotate(0turn)"},
{transform: "rotate(0turn) translate(3px) rotate(1turn)"}
],
{duration, iterations: Infinity, iterationStart: 0.9, easing}
));
}
});
movement.forEach(function (move) {
move.startTime = start_time;
});
}());
(function start_digital_clock() {
const clock = document.getElementById("digital-clock");
if (clock === null) {
return;
}
const milliseconds = clock.querySelector(".milliseconds");
const seconds = clock.querySelector(".seconds");
const minutes = clock.querySelector(".minutes");
const hours = clock.querySelector(".hours");
const sections = [seconds, minutes];
const durations = [s, m, h];
const animations = [];
Array.from(
milliseconds.children
).reverse().forEach(function (digit, index) {
animations.push(digit.firstElementChild.animate(
single_digit_keyframes,
{
duration: ms * (10 ** (index + 1)),
iterations: Infinity,
easing: "steps(10, end)"
}
));
});
sections.forEach(function (section, index) {
Array.from(
section.children
).forEach(function (digit) {
const nr_digits = digit.firstElementChild.children.length;
animations.push(digit.firstElementChild.animate(
single_digit_keyframes,
{
duration: (
nr_digits === 10
? durations[index] * 10
: durations[index + 1]
),
iterations: Infinity,
easing: `steps(${nr_digits}, end)`
}
));
});
});
Array.from(hours.children).forEach(function (digit) {
const nr_digits = digit.firstElementChild.children.length;
animations.push(
digit.firstElementChild.animate(
double_digit_keyframes,
{
duration: d,
iterations: Infinity,
easing: `steps(${nr_digits}, end)`
}
)
);
});
animations.forEach(function (animation) {
animation.startTime = start_time;
});
}());
(function set_up_date_complication() {
const day = document.querySelector(".day");
if (day === null) {
return;
}
function month() {
const now = new Date();
return digits(
(new Date(now.getFullYear(), now.getMonth() + 1, 0)).getDate(),
false
);
}
function create_animation(digit) {
const nr_digits = digit.firstElementChild.children.length;
const duration = d * nr_digits;
return digit.firstElementChild.animate(
double_digit_keyframes,
{
duration,
easing: `steps(${nr_digits}, end)`,
iterationStart: (d * ((new Date()).getDate() - 1)) / duration
}
);
}
const new_day = day.cloneNode();
new_day.append(month());
day.replaceWith(new_day);
Array.from(new_day.children).forEach(function (digit) {
const complication = create_animation(digit);
complication.startTime = start_time;
complication.finished.then(set_up_date_complication);
});
}());
这个时候,右侧的预览画面会实时进行更新,我们会看到时钟神奇地动了起来。
最后,我们把这个应用发布到到 Git 仓库中。
Cloud Studio 支持将代码和项目的仓库地址进行关联,支持 github/ Coding/ gitee 等多平台。目前,Web 时钟的代码已公开在 Coding 仓库中,你可以点击链接进行查看。
通过这么一系列操作下来,我感觉似乎和我在日常开发中使用 VSCode 自己开发,轻便快捷多了。省去了我很多保存文件、配置信息等等的步骤。最后来总结一下使用体验吧。
使用 Cloud Studio 这个在线 IDE 的感受非常好,它的优点非常丰富,我总结了一些我认为值得赞赏的地方:
虽然 Cloud Studio 表现优异,但也存在一些缺点:
但是,相较于其他 IDE,Cloud Studio 的优点在于它的轻便、易用、可扩展、云端存储等。但也有一些不同,例如它不能离线运行,并且可能会受到网络延迟的影响。总的来说,Cloud Studio 是一款非常方便的在线 IDE,可以让我们在任何地方方便地进行开发。