[JavaScript案例练习 | 青训营笔记]

135 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第9天。

概要:尝试编写一些学员手册中提供的案例来巩固加深JavaScript中的知识点。

一、题目要求:

1.笑话生成器

项目简介

我们提供了一些原始的 HTML / CSS,以及若干字符串和 JavaScript 函数,还需要你来编写一些 JavaScript 代码让项目运行起来:

  • 点击“随机生成笑话”按钮时生成一则笑话。
  • 若“生成”按钮按下之前,你在“输入自定义的名字”文字框中输入了一个自定义名字,那么生成的笑话中原有的名字(李雷 / Bob)将被取代。
  • 通过选择国家名称的单选按钮来确定界面语言以及笑话中温度和重量的制式。
  • 点一次按钮,生成一个新故事。点一次生成一个……

步骤

以下是你的工作。

初始化变量和函数:

  1. 将刚下载的文本文件中的“1. 定义变量和函数”标题项下所有代码复制粘贴至 main.js 中。此时你就有了三个变量(customName 是对“输入自定义的名字”文本框的引用,randomize 是对“随机生成笑话”按钮的引用,story 是对 HTML 底部的、准备存放笑话的 <p> 元素的引用)和一个函数(randomValueFromArray() 取一个数组作参数,随机返回数组中的一个元素)。

  2. 然后是文本文件的第二节——“2. 纯文本字符串”。这里包含了一些字符串,这些字符串是项目的输入信息。你应该在 main.js 文件中用变量来保存它们。

    1. 用 storyText 变量保存第一个长字符串,“今天气温……”。
    2. 用 insertX 数组保存第一组三个字符串,“怪兽威利……”。
    3. 用 insertY 数组保存第二组三个字符串。“肯德基……”。
    4. 用 insertZ 数组保存第三组三个字符串。“自燃了……”。

放置事件处理器并补全:

  1. 返回刚才的文本文件。

  2. 将“3. 事件监听器和未完成的函数定义”标题项下的代码复制粘贴至 main.js 文件。这将:

    • 为 randomize 变量增加一个点击事件的监听器。于是当所引用的按钮被点击时,result() 函数就会运行。
    • 为代码添加一个未完成的 result() 函数定义。本测验剩下的工作就是完成这个函数,让程序正常运行起来。

补全 result() 函数:

  1. 将 newStory 的值设置为 storyText。声明新变量有必要的,只有这样才能在每次按下按钮后、在函数运行时生成一个新的随机笑话。如果直接操作 storyText 则只能生成一次新故事

  2. 将 xItemyItem 和 zItem 分别设置为 randomValueFromArray(insertX)randomValueFromArray(insertY) 和 randomValueFromArray(insertZ)

  3. 接下来将 newStory 中的占位符(:inserta::insertb: 和 :insertc: )替换为 xItemyItem 和 zItem。有专用的字符串方法可供使用,并用该方法的返回值为 newStory 赋值。每当按下按钮时,这些占位符都会替换为随机的笑话字符串。再给你一点提示,我们所说的这种方法每次只会将所找到的首个子字符串进行替换,因此该方法对某个占位符需要执行两次。

  4. 在第一个 if 块中再次调用这个字符串替换方法,以使 newStory 字符串中的名字“李雷”替换为变量 name 的值。这里我们说:“如果 customName 中有值,就把故事里的“李雷”替换成它。”如果是汉化版将 newStory 中的“李雷”替换成 name 的值;

  5. 在第二个 if 块中检查 american 单选按钮是否被选中。如果选中,就要将故事中的重量和温度值从公斤和摄氏度转换为磅和华氏度,具体事项如下:

    1. 确定英美单位的转换公式。
    2. 定义变量 weighttemperature 的行中,分别将美制单位按公式转化为英制,用 Math.round() 对计算结果取整。然后将英式单位连接到末尾。
    3. 就在上述两个变量的定义下方增加两个字符串置换操作,将“35 摄氏度”替换为 temperature 的值,将“140 公斤”替换为 weight 的值。
  6. 最后,在函数倒数第二行,将 story.textContent(程序中显示笑话结果的段落)赋值为 newStory

提示

  • 除了在 HTML 文件中引入这个 JavaScript 文件之外,完全不需要编辑 HTML。

  • 如果你不确定当前 JavaScript 是否正确添加到了你的 HTML 中,可以尝试暂时删除 JavaScript 文件的所有内容,然后加上一些简单但效果显著的 JavaScript 代码,保存文件并刷新浏览器。下面的示例会让 <html> 背景变为红色,如果 JavaScript 成功加载,那么整个浏览器窗口将变红:

    document.querySelector('html').style.backgroundColor = 'red';
    

    Copy to Clipboard

  • Math.round() 是 Javascript 的内建函数,可取得与传入小数最接近的整数。

  • 本示例中有三类字符串需要替换。可以多次重复 replace() 方法,也可使用正则表达式。例如:var text = 'I am the biggest lover, I love my love'; 或 text.replace(/love/g,'like'); 会将所有的“love”替换为“like”。记住,字符串本身是不可修改的!

2.图片库

我们提供了一些 HTML、CSS、照片和几行 JavaScript 代码。需要你来编写必要的 JavaScript 代码让这个项目运行起来。HTML 的 body 部分如下:

<h1>Image gallery example</h1>

<div class="full-img">
  <img class="displayed-img" src="images/pic1.jpg">
  <div class="overlay"></div>
  <button class="dark">Darken</button>
</div>

<div class="thumb-bar">

</div>

Copy to Clipboard

该示例如下所示:

以下是本例中 CSS 文件最值得关注的部分:

  • full-img <div> 中有三个绝对定位的元素:一个显示全尺寸图片的 <img>,一个空 <div>(覆盖于 <img> 之上,且与之大小相同,用来设置半透明背景色来使图片变暗),和一个用来控制变暗效果的 <button>
  • 将 thumb-bar <div> 中图片(即“缩略图”)的宽度设置为 20%,并且将它们沿左侧浮动,使得它们在同一行上依次排列。

JavaScript 部分则需要实现:

  • 声明一个 const 数组,用于列出每张图像的文件名,例如 'pic1.jpg'
  • 迭代数组中的文件名,为每一个文件名创建一个 <img> 元素,并将其插入到 thumb-bar <div> 中,这样图片就会嵌入页面。
  • 为 thumb-bar <div> 里的每个 <img> 元素添加一个 click 事件监听器,在图片被点击时相应的图片将被显示到 displayed-img <img> 元素上。
  • 给 <button> 元素添加一个 click 事件监听器,当按钮被点击时,将全尺寸图片变暗,再次点击时取消。

详细步骤

以下是你的工作。

声明图像文件名数组

你需要创建一个用于列出所有需要包含在图片库中的图像的文件名。这个数组应该以常量的形式声明。

迭代照片

我们提供的代码中用一个名为 thumbBar 的变量用来存储 thumb-bar <div> 的引用,创建了一个新的 <img> 元素,将它的 src 属性值设置成 xxx 占位符,并且将这个新的 <img> 元素添加到 thumbBar 中。

你需要:

  1. 将“Looping through images”注释下方的代码放到一个循环之中,这个循环会迭代数组中所有的文件名。
  2. 对于每一次循环迭代,将占位符 xxx 替换为代表图像路径的字符串。我们需要设置每一个图像的 src 属性。请注意,每一张图像都在 images 文件夹内,文件名为 pic1.jpgpic2.jpg,等等。

为每一个缩略图添加 click 事件监听器

每次迭代中,你需要给当前的 newImage 加上一个 click 事件监听器——它应该查询当前图像的 scr 属性值,然后将获取的 src 属性值设置为 displayed-img <img> 元素 src 属性的值。

或者,你可以只为缩略图栏(thumb-bar <div>)添加事件监听器。

为变亮/变暗按钮编写处理器

最后还剩变亮/变暗的 <button>。我们已经提供了一个名为 btn 的变量来存储 <button> 的引用。需要添加一个 click 事件监听器:

  1. 检查当前 <button> 按钮的类名称,你可以使用 getAttribute() 方法获得。
  2. 如果类名是 "dark",则将 <button> 的类名变为 "light"(使用 setAttribute()),文本内容变为“Lighten”,然后将蒙板 <div> 的 background-color 设为 "rgba(0,0,0,0.5)"
  3. 如果类名不是 "dark",则将 <button> 的类名变为 "dark",文本内容变为 "Darken",然后将蒙板 <div> 的 background-color 设为 "rgba(0,0,0,0)"

以下是实现上述 2、3 点所提功能的基本代码:

btn.setAttribute('class', xxx);
btn.textContent = xxx;
overlay.style.backgroundColor = xxx;

Copy to Clipboard

提示

  • 完全不需要修改 HTML 和 CSS 文件。

3.序列动画

我们希望更新此页面,来将动画依次应用于所有三个图像。也就是说,当第一个动画完成时,我们开始第二个动画,当第二个完成时,我们开始第三个动画。

要应用的动画已经在“main.js”中被定义:效果是旋转图像并缩小它至消失。

完成步骤

设置第一个图像的动画

我们使用 Web Animations API 对图像进行动画处理,特别是 element.animate() 方法。

更新“main.js”以添加对 alice1.animate() 的调用,就像这样:

const aliceTumbling = [
  { transform: 'rotate(0) scale(1)' },
  { transform: 'rotate(360deg) scale(0)' }
];

const aliceTiming = {
  duration: 2000,
  iterations: 1,
  fill: 'forwards'
}

const alice1 = document.querySelector("#alice1");
const alice2 = document.querySelector("#alice2");
const alice3 = document.querySelector("#alice3");

alice1.animate(aliceTumbling, aliceTiming);

Copy to Clipboard

重载页面,你应该就会看到第一个图像旋转收缩的动画。

设置所有图像的动画

接下来,我们希望在 alice1 完成时启动 alice2 的动画,在 alice2 完成时启动 alice3 的动画。

animate() 方法返回 Animation 对象。这个对象有一个 finished 属性,这是会在一个在动画播放结束时兑现(fulfilled)的 Promise。所以我们可以利用这个 Promise 获得开始下一个动画的时机。

我们希望你尝试一些不同的方式来实现这个功能,以便于加强对使用 Promise 的不同方式的理解。

  1. 首先,实现一个能够工作的代码,但它存在“回调地狱”问题(我们在关于回调的讨论中提到过)的 Promise 版本。
  2. 接下来,使用 Promise 链来实现它。注意:可以用箭头函数的不同形式来编写这个函数。尝试这些不同的形式。哪个最简洁?哪个可读性最好?
  3. 使用 async 和 await 来实现它。

别忘了,element.animate() 并不返回一个 Promise:它返回一个 Animation 对象,该对象具有一个 finished 属性,这个属性才是 Promise

二、我实现的效果:

1.笑话生成器

image.png

image.png

2.图片库

image.png

3.序列动画

image.png

image.png

三、JavaScript实现代码:

1.笑话生成器

const customName = document.getElementById('customname');
const randomize = document.querySelector('.randomize');
const story = document.querySelector('.story');

function randomValueFromArray(array){
  const random = Math.floor(Math.random() * array.length);
  return array[random];
}

let storyText = '今天气温 35 摄氏度,:insertx:出门散步。当走到:inserty:门前时,突然就:insertz:。人们都惊呆了,李雷全程目睹但并没有慌,因为:insertx:是一个 140 公斤的胖子,天气又辣么热。';
let insertX = ['怪兽威利', '大老爹', '圣诞老人'];
let insertY = ['肯德基', '迪士尼乐园', '白宫'];
let insertZ = ['自燃了', '在人行道化成了一坨泥', '变成一只鼻涕虫爬走了'];

randomize.addEventListener('click', result);

function result() {
  let newStory = storyText;

  let xItem = randomValueFromArray(insertX);
  let yItem = randomValueFromArray(insertY);
  let zItem = randomValueFromArray(insertZ);

  newStory = newStory.replace(':insertx:', xItem);
  newStory = newStory.replace(':insertx:', xItem);
  newStory = newStory.replace(':inserty:', yItem);
  newStory = newStory.replace(':insertz:', zItem);

  if(customName.value !== '') {
    const name = customName.value;
    newStory = newStory.replace('李雷', name);
  }

  if(document.getElementById("american").checked) {
    const weight = Math.round(140 * 2.20462) + ' 磅';
    const temperature =  Math.round(35 * 9 / 5 + 32) + ' 华氏度';
    newStory = newStory.replace('35 摄氏度', temperature);
    newStory = newStory.replace('140 公斤', weight);
  }

  story.textContent = newStory;
  story.style.visibility = 'visible';
}

2.图片库

const displayedImage = document.querySelector('.displayed-img');
const thumbBar = document.querySelector('.thumb-bar');

const btn = document.querySelector('button');
const overlay = document.querySelector('.overlay');

/* Declaring the array of image filenames */
const images=["pic1.jpg","pic2.jpg","pic3.jpg","pic4.jpg","pic5.jpg"];
/* Declaring the alternative text for each image file */

    const pic1=document.createElement('img');
    pic1.src="images/pic"+1+".jpg";
    pic1.className="displayed-img";
    pic1.onclick=function(){
        displayedImage.src="images/pic"+1+".jpg";
    }
    thumbBar.appendChild(pic1);

    const pic2=document.createElement('img');
    pic2.src="images/pic"+2+".jpg";
    pic2.className="displayed-img";
    pic2.onclick=function(){
        displayedImage.src="images/pic"+2+".jpg";
    }
    thumbBar.appendChild(pic2);

    const pic3=document.createElement('img');
    pic3.src="images/pic"+3+".jpg";
    pic3.className="displayed-img";
    pic3.onclick=function(){
        displayedImage.src="images/pic"+3+".jpg";
    }
    thumbBar.appendChild(pic3);

    const pic4=document.createElement('img');
    pic4.src="images/pic"+4+".jpg";
    pic4.className="displayed-img";
    pic4.onclick=function(){
        displayedImage.src="images/pic"+4+".jpg";
    }
    thumbBar.appendChild(pic4);

    const pic5=document.createElement('img');
    pic5.src="images/pic"+5+".jpg";
    pic5.className="displayed-img";
    pic5.onclick=function(){
        displayedImage.src="images/pic"+5+".jpg";
    }
    thumbBar.appendChild(pic5);


    // for(i=1;i<=5;i++){
    // const pic=document.createElement('img');
    // pic.src="images/pic"+i+".jpg";
    // pic.className="displayed-img";
    // pic.onclick=function(i){
    //     displayedImage.src="images/pic"+i+".jpg";
    // }
    // thumbBar.appendChild(pic);
    // }



/* Wiring up the Darken/Lighten button */
btn.onclick=function(){
    const temp=btn.getAttribute('class');
    if(temp==='dark'){
          btn.setAttribute('class','light');
          btn.textContent="Lighting";
    overlay.style.backgroundColor='rgba(0,0,0,0.5)';
    }else{
        btn.textContent="Darken";
    overlay.style.backgroundColor='rgba(0,0,0,0)';
        btn.setAttribute('class','dark');
    }
}

3.序列动画


const aliceTumbling = [
  { transform: 'rotate(0) scale(1)' },
  { transform: 'rotate(360deg) scale(0)' }
];

const aliceTiming = {
  duration: 2000,
  iterations: 1,
  fill: 'forwards'
}

const alice1 = document.querySelector("#alice1");
const alice2 = document.querySelector("#alice2");
const alice3 = document.querySelector("#alice3");



alice1.animate(aliceTumbling, aliceTiming).finished
.then(()=>   alice2.animate(aliceTumbling, aliceTiming).finished)
.then(()=>   alice3.animate(aliceTumbling, aliceTiming).finished).finished;



四、个人练习总结:

熟悉了javascript知识点和实际编程操作,在实践中检验了之前的学习成果,感觉得到了知识巩固和加强。