js做一个简易钢琴

原生js实现简易钢琴


js能干什么?控制页面逻辑,完成行为的控制?其实js的api极其丰富,今天我们就用js来做一个简易的钢琴来体会

一下js的强大之处吧

API介绍

首先我们来了解一下下面几个关于音频的API

创建音频上下文

我们需要在页面中创建音频上下文,才可以进行操作

var ctx = new Audiocontext()

需要注意:

  • 音频上下文不能在页面加载时直接创建
  • 上下文创建后自动启动,不可停止
  • 上下文创建后相当于一个时间轴,一直向后延伸,我们后续可以通过对象控制发声的时间和频率等,也因此建议只创建一个音频上下文,防止时间轴紊乱
  • 属性 ctx.current 代表在当前的时间点标一个点,延伸向未来

获取音量控制对象

获得该对象已控制音量

var g = ctx.createGain()

该对象有一个操作:

  • 方法 connect

    将该对象连接到制定设备,这里直接连接到上下文的默认扬声器

    g.connect(ctx.desination)
    
  • 属性 gain.value

    控制音量音高 (0 - 1)

  • 方法 gain.linearRampToValueAtTime

    g.gain.linearRampToValueAtTime(0.6,ctx.currentTime + 0.01)
    //在0.01s时间内从音量音高从value值变到0.6
    
  • 方法 .gain.exponentialRampToValueAtTime

    g.gain.exponentialRampToValueAtTime(0.01 , ctx.currentTime + duration)
    //音高在duration时间内由原value变为0.01
    

获取振荡器对象

只获得上下文是无法进行更多操作的,我们还需要获得振荡器对象,来控制音色音高

var osc = ctx.createOscillator();

振荡器对象的属性或方法:

  • 方法 connect

    获得振荡器后,我们还需要将其连接到音量控制器,不连接不会发声

    osc.connect(g)
    
  • 属性 type

    音频震荡的波形,不同波形产生不同的音色 有下面四个值

    • sine:默认值,正弦波
    • square:方形波
    • sawtooth:锯齿波
    • triangle:三角波
  • 属性 frequency.value

    频率越高,声音越高

    按照第一国际高音,标准中音6的频率是440HZ

    这里我们的简易钢琴需要的频率区间是

    以下是按照第一国际高音,从低音1,到高音7的频率(不包含小数)

    [130,147,165,175,196,220,246,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1047]

  • 方法 start

    在制定的音频上下文时间播放声音

  • 方法 stop

    在制定的音频上下文时间停止播放


上面的这么多API是不是有些复杂和难记?别急,我们把它封装一下,咱们跟着代码走一遍

首先是创建上下文函数

var ctx;
function setContext(){
    if(!ctx){
        ctx = new AudioContext()
    }
}

发声函数

function makeSound(index){               // 钢琴传入是钢琴的第几个按键
  setContext();                         //获得上下文
  var osc = ctx.createOscillator()        //获得音频振荡器
  var g = ctx.createGain()               //获得音量控制器
  osc.connect(g)                        //连接音量控制器
  osc.type = 'sine'                     //设置波形
  osc.frequency.value = sounds[index]     //对应钢琴不同键的不同频率
  var duration = 1                      //控制时间
  g.connect(ctx.destination)             //连接扬声器
  g.gain.value = 0                      //初始音高为0
  osc.start();                          //从当前开始发生
  g.gain.linearRampToValueAtTime(0.6,ctx.currentTime + 0.01)    //从当前时间到0.01s后,音高从0~0.6
  osc.stop(ctx.currentTime + duration);                       //当前时间duration后停止
  g.gain.exponentialRampToValueAtTime(0.01 , ctx.currentTime + duration)    //从0.6~0.01

HTML CSS

<body>
    <ul class="container">
        <li key='1' class="low"><span>1</span></li>
        <li key='2' class="low"><span>2</span></li>
        <li key='3' class="low"><span>3</span></li>
        <li key='4' class="low"><span>4</span></li>
        <li key='5' class="low"><span>5</span></li>
        <li key='6' class="low"><span>6</span></li>
        <li key='7' class="low"><span>7</span></li>
        <li key='q'><span>1</span></li>
        <li key='w'><span>2</span></li>
        <li key='e'><span>3</span></li>
        <li key='r'><span>4</span></li>
        <li key='t'><span>5</span></li>
        <li key='y'><span>6</span></li>
        <li key='u'><span>7</span></li>
        <li key='a' class="high"><span>1</span></li>
        <li key='s' class="high"><span>2</span></li>
        <li key='d' class="high"><span>3</span></li>
        <li key='f' class="high"><span>4</span></li>
        <li key='g' class="high"><span>5</span></li>
        <li key='h' class="high"><span>6</span></li>
        <li key='j' class="high"><span>7</span></li>
    </ul>
</body>
body{
    width: 100%;
    height: 100%;
    background: url(img/img.jpg) no-repeat;
    background-size: 100% 100%;
    overflow: hidden;
    user-select: none;
}

.container{
    text-align: center;
    margin: 200px auto;
}

li{
    position: relative;
    display: inline-block;
    width:50px;
    height: 200px;
    background-color: #fff;
    border-radius: 5px;
    cursor: pointer;
    font-size: 1.5em;
    box-shadow: 0  0  5px 3px rgba(0, 0, 0, 0.5);
}

li span{
    position: absolute;
    width: 100%;
    left: 0;
    bottom: 20px;
}

.low::after{
    content: '.';
    position: absolute;
    left: 0;
    bottom: 10px;
    width: 100%;

}

.high::after{
    content: '.';
    position: absolute;
    left: 0;
    bottom: 45px;
    width: 100%;
}

剩余javaScript逻辑


var ctx;
var sounds = [130,147,165,175,196,220,246,262,294,330,349,392,440,494,
    523,587,659,698,784,880,988,1047]
//点击钢琴样式改变
function changeColor(li){
    
    li.style.background = '#ccc';
    setTimeout(function(){
        li.style.background = '#fff';
    } , 100)
}

var container = document.getElementsByClassName('container')[0];

//函数控制钢琴键点击事件钢琴键样式的改变
container.onclick = function(e){

    if(e.target.tagName === 'UL'){
        return;
    }

    li = e.target;
    //点击到SPAN要控制父级变色
    if(e.target.tagName == 'SPAN'){
        li = e.target.parentNode
    }
   
    changeColor(li);
    //获得钢琴是第几键
    var index = getIndex(li);
    makeSound(index);
}


window.onkeydown = function(e){
    //属性选择器
    var selector = "li[key=\"" + e.key + "\"]"  
   
    var li = document.querySelector(selector);
    
    if(li){
        this.changeColor(li);
        var index = getIndex(li);
        makeSound(index);
    }
}


function setContent(){
    if(!ctx){
        ctx = new AudioContext()
    }
}

function getIndex(li){
    var ul = li.parentNode;
    //Array.from(类数组)返回真数组
    var children = Array.from(ul.children)
    return children.indexOf(li);
}

function makeSound(index){
    // 获得音频上下文
    setContent();
    //得到音频振荡器
    var osc = ctx.createOscillator();
    //得到音量控制对象
    var g = ctx.createGain();

    // 连接振荡器和音量控制对象
    osc.connect(g);
    osc.type = 'sine';
    osc.frequency.value = sounds[index];

    var duration = 1;

    g.connect(ctx.destination);
    g.gain.value = 0;
    osc.start();
    g.gain.linearRampToValueAtTime(0.6,ctx.currentTime + 0.01)
    
    osc.stop(ctx.currentTime + duration);
    g.gain.exponentialRampToValueAtTime(0.01 , ctx.currentTime + duration)

    
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容