skip to Main Content

I’m developing a piano keyboard using HTML + CSS.
My code structure is as follows:

:root {
  --piano-width: 600px;
  --piano-height: 150px;
  --piano-keys-gap: 1px;
  --piano-black-keys-height-perc: 0.6;
}

.piano-keys {
  display: flex;
  flex-direction: row;
  justify-content: center;
  width: var(--piano-width);
  height: var(--piano-height);
}

.piano-key {
  position: relative;
  box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.2);
  cursor: pointer;
}

.piano-key.key-white {
  background-color: #eeeeee;
  flex-basis: 100%;
}

.piano-key.key-black {
  background-color: #333;
  flex-basis: 90%;
  height: calc(var(--piano-height) * var(--piano-black-keys-height-perc));
  z-index: 1;
  margin-left: -HALF_CURRENT_SIZE;
  margin-right: -HALF_CURRENT_SIZE;
}
<div class="piano-container">
  <div class="piano-keys">
    <div class="piano-key key-white note-selector" data-note="C" selected>C</div>
    <div class="piano-key key-black note-selector" data-note="C#" has-acidental>C#</div>
    ...
    <div class="piano-key key-white note-selector" data-note="B">B</div>
  </div>
</div>

I want a way to position the black keys over the white keys, considering that:

  • The width of the keys is dynamic, they must fill the size of the parent div
  • The width of the black keys is a little smaller than the width of the white keys (about 100% flex basis for white, and 90% flex basis for black)
  • I want to be able to control the GAP between the keys, in px

I found on a website that by placing a negative horizontal margin of half the width of the key on each side, it overlaps, with the desired effect. But since the width is dynamic, I couldn’t do this.
Is there a way to achieve this effect without using JavaScript?

2

Answers


  1. Just a proposal…

    // just 4 testing...
    
    const pianoKeys = document.querySelector('.pianoKeys')
      ;
    let timConsole = 0
      ;
    pianoKeys.onclick = e =>
      {
      clearTimeout(timConsole);
      console.clear();
      if (!e.target.matches('div[data-note]')) return;
      console.log( e.target.dataset.note );
      timConsole = setTimeout(console.clear,2000);
      }
    :root {
      --gapWk :   2px; /* gap White keys         */
      --szwWk :  50px; /* size width White keys  */
      --szwBk :  40px; /* size width Black keys  */
      --szhWk : 100px; /* size height White keys */
      --szhBk :  50px; /* size height Black keys */
      }
    *,html,body {
      margin     : 0;
      padding    : 0;
      box-sizing : border-box;
      }
    body {
      font-family : Arial, Helvetica, sans-serif;
      font-size   : 16px;
      }
    .pianoKeys {
      position  : relative;
      font-size : 0;
    
      & > div > div {
        font-size: 1rem;
        text-align: center;
        }
      & > div > div::before {
        content : attr(data-note);
        }
      & > .BlackKeys , 
      & > .whiteKeys {
        position    : absolute;
        top         : 0;
        left        : 0;
        white-space : nowrap;
        z-index     : -1;
        }
      & > .BlackKeys > div , 
      & > .whiteKeys > div {
        display    : inline-block;
        box-sizing : border-box;
        z-index    : 10;
        }
      }
    .BlackKeys {
      padding-left : calc((var(--szwWk) - (var(--szwBk) / 2)) + (var(--gapWk) / 2));   
      background   : #7fc1ff80;
      height       : 1px;
      overflow     : visible;
      }
    .whiteKeys > div {
      width        : var(--szwWk);
      height       : var(--szhWk);
      background   : whitesmoke;
      border       : 1px solid black;
      margin-right : var(--gapWk);
      padding-top  : calc(var(--szhWk) - 1.6em) ;
      }
    .BlackKeys > div {
      width        : var(--szwBk);
      height       : var(--szhBk);
      background   : darkslategrey;
      margin-right : calc(var(--szwWk) - var(--szwBk) + var(--gapWk));
      color        :  yellow;
      padding-top  : calc(var(--szhBk) - 2em) ;
      }
    .BlackKeys > div[data-note="a#"] ,
    .BlackKeys > div[data-note="d#"] {
      margin-right : calc((var(--szwWk) *2) - var(--szwBk) + (var(--gapWk) *2));
      }
      
      
    .as-console-row       { background-color: yellow; }
    .as-console-row::after{ display:none !important;  }
    .as-console-row-code  { font-size: 30px !important; font-weight: bold !important; }
    <div class="pianoKeys">
      <div class="whiteKeys">
        <div data-note="c"></div>
        <div data-note="d"></div>
        <div data-note="e"></div>
        <div data-note="f"></div>
        <div data-note="g"></div>
        <div data-note="a"></div>
        <div data-note="b"></div>
        <div data-note="c"></div>
        <div data-note="d"></div>
        <div data-note="e"></div>
        <div data-note="f"></div>
        <div data-note="g"></div>
        <div data-note="a"></div>
        <div data-note="b"></div>
      </div>
      <div class="BlackKeys">
        <div data-note="c#"></div>
        <div data-note="d#"></div>
        <div data-note="f#"></div>
        <div data-note="g#"></div>
        <div data-note="a#"></div>
        <div data-note="c#"></div>
        <div data-note="d#"></div>
        <div data-note="f#"></div>
        <div data-note="g#"></div>
        <div data-note="a#"></div>
      </div>
    </div>
    Login or Signup to reply.
  2. The example below is the full 88 keys with natural, flat, and sharp notes labeled. There are comments for .black-key dimensions if you want a more accurate scale and more space between them. In a responsive layout an absolute unit like px isn’t very optimal. Positioning of .black-key was done with negative margin-left. The majority of the lengths are in rem units which are relative to the :root font-size which is based on vmax units which is based on the larger length of the viewport. Basically if the window’s width is bigger than the height, then all rems will scale to that and vice versa should the window’s height be greater than it’s width. View in Full page mode and resize the window to review it’s responsiveness. Details are commented in the example.

    /* vmax is relative to the larger sized length of viewport */
    /* rem is relative to the font-size declared on <html>
     (aka :root) */
    :root {
      font: 2vmax/1 "Roman Times";
    }
    
    main {
      display: flex;
      flex-flow: row nowrap;
      justify-content: center;
      width: min-content;
      margin: 20px auto;
      border: 3px solid black;
      border-bottom-left-radius: 3px;
      border-bottom-right-radius: 3px;
      background-color: black;
      cursor: pointer;
    }
    
    
    /* 150mm long 23.5mm wide */
    /* All flexbox properties align the text */
    .key {
      display: flex;
      flex-flow: column nowrap;
      justify-content: flex-end;
      align-items: center;
      width: 1.2rem;
      height: 7.5rem;
      border: 1px solid black;
      border-bottom-left-radius: 3px;
      border-bottom-right-radius: 3px;
      background: white;
    }
    
    /* Shift all .key that proceed a .black-key shifts left */
    .black-key+.key {
      margin-left: -0.6rem
    }
    
    /* Display [data-note] */
    .key::after {
      content: attr(data-note);
    }
    
    /* Change background for as long as user has pointer down */
    .key:active {
      background: rgba(255, 255, 255, 0.8);
    }
    
    
    /* 90mm long 13.7mm wide */
    /* position and z-index keeps .black-key above .key */
    /* Flexbox properties align text */
    /* margin-left shifts to the left, margin-bottom pushes 
    it to the top */
    .black-key {
      position: relative;
      z-index: 1;
      display: flex;
      flex-flow: column nowrap;
      justify-content: flex-end;
      align-items: center;
      width: 1.2rem;
      /* width: 0.675rem more accurate scale */
      height: 4.5rem;
      margin-left: -0.6rem;
      /* margin-left: -0.3375rem more accurate scale */
      margin-bottom: auto;
      border-bottom-left-radius: 3px;
      border-bottom-right-radius: 3px;
      color: white;
      background: black;
    }
    
    /* Display [data-flat]♭ */
    .black-key::before {
      content: attr(data-flat)"0266d";
    }
    
    /* Display [data-sharp]♯ */
    .black-key::after {
      content: attr(data-sharp)"0266f";
    }
    
    /* Change background for as long as user has pointer down */ 
    .black-key:active {
      background: rgb(39, 39, 39);
    }
    <main>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
      <div class="black-key" data-flat="D" data-sharp="C"></div>
      <div class="key" data-note="D"></div>
      <div class="black-key" data-flat="E" data-sharp="D"></div>
      <div class="key" data-note="E"></div>
      <div class="key" data-note="F"></div>
      <div class="black-key" data-flat="G" data-sharp="F"></div>
      <div class="key" data-note="G"></div>
      <div class="black-key" data-flat="A" data-sharp="G"></div>
      <div class="key" data-note="A"></div>
      <div class="black-key" data-flat="B" data-sharp="A"></div>
      <div class="key" data-note="B"></div>
      <div class="key" data-note="C"></div>
    </main>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search