心血来潮,这次我用代码“敲”木鱼

3,752 阅读6分钟

背景

最近真是压力大到掉头发?老妈默默一句是不是又瘦了突然让我感慨万千,嗯 还是想跟自己说一声加油吧!近些日子以来,头发有在减少,所以想多给自己“加加功德”,于是想起了很久之前下载的一款木鱼类App,便打开它敲了起来,但使用体验不是很好!每敲几下(甚至才敲了1下),就弹出了广告,而看到广告的瞬间,我已经不由自主的敲完了第2下,并不出乎意料的是下一秒就跳到了广告页面......。嗯 所以没有什么能难得倒程序员,如果有,那就是素材不够。好了,避开废话不谈,下面步入正题

分析

需求

页面如下图所示,不过在每一次敲击时,都会伴随4个同时发生的动作:

  1. 木鱼进行一次缩放
  2. 弹出文字浮层“功德+1”,且文字透明度由1变0
  3. 发起一次手机震动
  4. 播放敲击木鱼的声音

wooden_fish_page.png

技术栈

面对这种寿命短,后期也基本不需要维护的项目(更没有复杂的网络请求一说),本篇文章直接使用原生JavaScript进行开发。或者您也可以尝试一下低代码

关于低代码,您大可放心的阅读此篇干货文章《低代码都做了什么?(为什么?怎么实现Low-Code?)》

至于TypeScript,您可以通过《谈谈写TypeScript实践而来的心得体会》这篇文章快速上手或进阶TS

实现

页面布局

wooden_fish_page_html.png

图中右侧标出了三个部分:

  1. img标签用于指定木鱼的图片url地址,在木鱼进行缩放时,对该标签增加/删除css类名即可
  2. 每次敲击时所产生的文字由p标签生成,且所有的p标签都存在于div标签之下
  3. audio标签会在敲击时播放声音

本篇文章不会涉及具体的Html、Css部分。如有疑问,请在此项目的GitHub中找到答案

逻辑部分

准备工作

通过JavaScript获取要操作的真实dom

const dom = {
    // 木鱼
    woodenFish: document.querySelector("img"),
    // 文字浮层
    text: document.querySelector(".w-f-c-text"),
    // 音频
    audio: document.querySelector("audio")
}

木鱼缩放

这里的思路是敲击时给img追加一个带有css animation的样式类,该animation的作用是让木鱼进行一次缩放,例如

.w-f-c-i-size {
    /** 这里的animation只会执行一次缩放,所以后面会通过增加/删除该类名来达到可以进行n次缩放的效果 */
    animation: wooden-fish-size 0.3s;
}

@keyframes wooden-fish-size {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(0.9);
  }
  100% {
    transform: scale(1);
  }
}

样式搞定之后,通过原生JavaScript提供的dom classList进行css样式类名的增加与删除。dom classList共有四个方法:

  1. add:在指定节点上增加一个样式类名
  2. remove:在指定节点上删除一个样式类名
  3. toggle:在指定节点A上若已有样式类名a,则将a删除;若没有样式类名a,则添加类名a
  4. replace:将指定节点上的样式类名替换为另一个样式类名。效果同String##replace一致
const woodenFish = {
    // 封装一个用于增加/删除类名的方法
    className(type) {
        dom.woodenFish.classList[type]("w-f-c-i-size")
    },
    size() {
        this.className("add")
        setTimeout(() => this.className("remove"), 300)
    }
}

size方法用于进行一次木鱼的缩放。调用该方法时,首先为img标签增加类w-f-c-i-size,在300毫秒后,再将该类名移除

为什么是300毫秒?因为css animation的持续时间为300毫秒

需要注意的是,size方法中的thiswoodenFish对象,所以this.className就相当于woodenFish.className

关于this或其它JavaScript的问题,您可以在《JavaScript每日一题》专栏中找到对应的题目进行练习

文字浮层

const woodenFish = {
    className() {},
    size() {},
    createText() {
        const p = document.createElement("p")
        p.innerText = "功德+1"
        dom.text.appendChild(p)
    }
}

createText方法用于创建一个p标签,该标签的文字内容为“功德+1”,随后将该标签追加在div下即可

小tip:JSX(或react)中书写HTML类型的注释

博主在此刻书写document.createElement这个原生方法时,突然想到了最近用到的一个原生属性outerHTML,该属性与innerHTML的区别就不再赘述。在JSX中书写html类型的注释,使用大括号的形式({/** */})是不可以的,因为在编译时这些东西都会被扔掉,此时可以使用由React提供的dangerouslySetInnerHTML属性,但体验感不太好。所以可以使用ouertHTML配合ref来解决,vue同理,例如:

const HtmlComment: FC<HtmlCommentType> = ({ children }) => {
    const virtual = useRef<HTMLSpanElement>(null)
    useEffect(() => {
        virtual.current!.outerHTML = `<!-- ${children} -->`
    }, [])
    return <span ref={virtual} />
}

H5控制手机震动

const vibrate = () => {
    const navigator = window.navigator
    if (!("vibrate" in navigator)) return
    navigator.vibrate =
        navigator.vibrate ||
        navigator.webkitVibrate ||
        navigator.mozVibrate ||
        navigator.msVibrate
    if (!navigator.vibrate) return
    // 上面的代码全是进行兼容性判断,只有下面这一行是发起手机震动的API
    navigator.vibrate(300)
}

像发起手机震动这类Api,首先就要进行兼容性判断,所以上面vibrate方法的90%部分都在进行兼容性判断。注意,window.navigator提供了一个用于发起设备震动的方法,即window.navigator.vibrate

window.navigator.vibrate方法的参数:

  1. 一个number类型的值

这种方式表示震动持续多长时间,例如window.navigator.vibrate(300),则表示震动持续300毫秒

  1. 一个number类型的数组

这种方式表示震动、暂停间隔的时间。例如window.navigator.vibrate([100, 30, 100]),则表示先震动100毫秒,随后暂停30毫秒,然后再震动100毫秒

window.navigator.vibrate方法在震动成功时返回true,否则返回false

vibrate兼容性

vibrate.png

浏览器全屏操作

const toggleFullScreen = () => {
    if (!document.fullscreenElement)
        return document.documentElement.requestFullscreen()
    if (!document.exitFullscreen) return
    document.exitFullscreen()
}

document.addEventListener("keydown", (e) =>
    e.keyCode == 13 ? toggleFullScreen() : false
)

toggleFullScreen方法会在全屏或非全屏之间来回切换,用到了以下属性/方法:

  1. document.fullscreenElement 返回当前正在以全屏模式显示的元素,如果没有,则返回null
  2. document.documentElement.requestFullscreen 用于发起全屏请求。若全屏请求成功,则该函数返回成功的Promise对象,否则返回失败的Promise对象。

在全屏成功时,全屏显示的元素会触发fullscreenchange事件;类似于输入框在输入时会触发onchange事件

  1. document.exitFullscreen 方法用于使当前元素退出全屏模式

随后为document绑定keydown事件,如果按下了回车键,则在全屏/非全屏之间切换,否则不做出任何操作

音频事件操作

之前博主在写播放器的时候就发现音频的属性、方法、事件很多很多,所以此处只列举两个本项目中用到的方法

  1. play()   使播放开始
  2. pause() 使播放暂停

制作完成

通过以上几个步骤就已经完成了所有要用到的东西,最后只需为木鱼注册“敲击”事件即可

dom.woodenFish.addEventListener("click", () => {
    // 木鱼缩放
    woodenFish.size()
    // 创建文字浮层
    woodenFish.createText()
    // 播放敲击木鱼的声音
    dom.audio.play()
    // 发起手机震动
    vibrate()
})

文末

从一次心血来潮,到自己从0至1完成这个简单而有趣的小项目,无论是技术角度,还是个人收获角度来讲,都是收获满满!现在您可以通过以下两个地址来 “功德+1” :

  1. 在线使用地址
  2. GitHub地址,该仓库会持续制作一些package,欢迎Star !

由于时间匆忙,文中错误之处在所难免,敬请读者斧正。如果您觉得本篇文章还不错,欢迎点赞收藏和关注,我们下篇文章见!