Search code examples
javascripthtmlcssdatetimereal-time-clock

How do I prevent "wobbling" of digital clock?


For my digital signage project I made a simple digital clock with date in JS. But when I align the clock on the right side of the top bar (which I need to do), the text shifts back and forth when the seconds update. This is due to the condensed font I am using (and I have to use it). Any ideas how to prevent that from happening?

Here's my code:

function startTime() {
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    var s = today.getSeconds();
    var y = today.getFullYear();
    var o = today.getMonth();
    var a = today.getDate();
    var d = today.getDay();

    var monthnames = new Array();
    monthnames[0] = "January";
    monthnames[1] = "February";
    monthnames[2] = "March";
    monthnames[3] = "April";
    monthnames[4] = "May";
    monthnames[5] = "June";
    monthnames[6] = "July";
    monthnames[7] = "August";
    monthnames[8] = "September";
    monthnames[9] = "Oktober";
    monthnames[10] = "November";
    monthnames[11] = "December";

    var weekdays = new Array();
    weekdays[0] = "Sunday";
    weekdays[1] = "Monday";
    weekdays[2] = "Tuesday";
    weekdays[3] = "Wednesday";
    weekdays[4] = "Thursday";
    weekdays[5] = "Friday";
    weekdays[6] = "Saturday";

    m = checkTime(m);
    s = checkTime(s);
    a = checkTime(a);


    document.getElementById('clock').innerHTML =
        `<h1>${weekdays[d]}, ${a} ${monthnames[o]}, ${y}, <span id="clocktime">${h}:${m}:${s}</h1>`;
    var t = setTimeout(startTime, 500);
}

function checkTime(i) {
    if (i < 10) { i = "0" + i };  // Fügt vor einstelligen Zahlen (i > 10) eine Null hinzu
    return i;
}

startTime()
@import url('https://fonts.googleapis.com/css?family=Ropa+Sans');
* {
  font-family: Ropa Sans;
  margin: 0;
}

.top-bar {
  width: 100%;
  height: 50px;
  background-color: #ccc;
  display: flex;
  justify-content: flex-end;
}

.time {
  margin-top: auto;
  margin-bottom: auto;
  margin-right: 1em;
}
<div class="top-bar">
  <div class="time" id="clock"></div>
</div>


Solution

  • You can't use a variable-width font and right-align and not have the text move around. One of those two things (variable-width or right-alignment) has to give way (well, probably; you might just minimize one). :-)

    You've said you have to use the variable-width font, so I have two workarounds (I think calling them "solutions" would be overkill :-) ):

    1. Put the time at the beginning. This minimizes the effect; it only happens when the day changes.

    2. Put the text left-aligned in a container that's at the maximum size you'll need, with the container right-aligned.

    3. Make just the clock digits fixed-width and see if anyone complains. :-)

    I didn't think of the third one until late, but #3 is probably your best bet.

    Here's an example of #1:

    function startTime() {
        var today = new Date();
        var h = today.getHours();
        var m = today.getMinutes();
        var s = today.getSeconds();
        var y = today.getFullYear();
        var o = today.getMonth();
        var a = today.getDate();
        var d = today.getDay();
    
        var monthnames = new Array();
        monthnames[0] = "January";
        monthnames[1] = "February";
        monthnames[2] = "March";
        monthnames[3] = "April";
        monthnames[4] = "May";
        monthnames[5] = "June";
        monthnames[6] = "July";
        monthnames[7] = "August";
        monthnames[8] = "September";
        monthnames[9] = "Oktober";
        monthnames[10] = "November";
        monthnames[11] = "December";
    
        var weekdays = new Array();
        weekdays[0] = "Sunday";
        weekdays[1] = "Monday";
        weekdays[2] = "Tuesday";
        weekdays[3] = "Wednesday";
        weekdays[4] = "Thursday";
        weekdays[5] = "Friday";
        weekdays[6] = "Saturday";
    
        m = checkTime(m);
        s = checkTime(s);
        a = checkTime(a);
    
    
        document.getElementById('clock').innerHTML =
            `<h1><span id="clocktime">${h}:${m}:${s}</span> - ${weekdays[d]}, ${a} ${monthnames[o]}, ${y}</h1>`;
        var t = setTimeout(startTime, 500);
    }
    
    function checkTime(i) {
        if (i < 10) { i = "0" + i };  // Fügt vor einstelligen Zahlen (i > 10) eine Null hinzu
        return i;
    }
    
    startTime()
    @import url('https://fonts.googleapis.com/css?family=Ropa+Sans');
    * {
      font-family: Ropa Sans;
      margin: 0;
    }
    
    .top-bar {
      width: 100%;
      height: 50px;
      background-color: #ccc;
      display: flex;
      justify-content: flex-end;
    }
    
    .time {
      margin-top: auto;
      margin-bottom: auto;
      margin-right: 1em;
    }
    <div class="top-bar">
      <div class="time" id="clock"></div>
    </div>

    Here's #2, using a temporary-and-probably-rubbish value for the width; you'll want to do more diligence on the width to use:

    function startTime() {
        var today = new Date();
        var h = today.getHours();
        var m = today.getMinutes();
        var s = today.getSeconds();
        var y = today.getFullYear();
        var o = today.getMonth();
        var a = today.getDate();
        var d = today.getDay();
    
        var monthnames = new Array();
        monthnames[0] = "January";
        monthnames[1] = "February";
        monthnames[2] = "March";
        monthnames[3] = "April";
        monthnames[4] = "May";
        monthnames[5] = "June";
        monthnames[6] = "July";
        monthnames[7] = "August";
        monthnames[8] = "September";
        monthnames[9] = "Oktober";
        monthnames[10] = "November";
        monthnames[11] = "December";
    
        var weekdays = new Array();
        weekdays[0] = "Sunday";
        weekdays[1] = "Monday";
        weekdays[2] = "Tuesday";
        weekdays[3] = "Wednesday";
        weekdays[4] = "Thursday";
        weekdays[5] = "Friday";
        weekdays[6] = "Saturday";
    
        m = checkTime(m);
        s = checkTime(s);
        a = checkTime(a);
    
    
        document.getElementById('clock').innerHTML =
            `<h1>${weekdays[d]}, ${a} ${monthnames[o]}, ${y}, <span id="clocktime">${h}:${m}:${s}</span></h1>`;
        var t = setTimeout(startTime, 500);
    }
    
    function checkTime(i) {
        if (i < 10) { i = "0" + i };  // Fügt vor einstelligen Zahlen (i > 10) eine Null hinzu
        return i;
    }
    
    startTime()
    @import url('https://fonts.googleapis.com/css?family=Ropa+Sans');
    * {
      font-family: Ropa Sans;
      margin: 0;
    }
    
    .top-bar {
      width: 100%;
      height: 50px;
      background-color: #ccc;
      display: flex;
      justify-content: flex-end;
    }
    
    .time {
      width: 450px;
      margin-top: auto;
      margin-bottom: auto;
      margin-right: 1em;
    }
    <div class="top-bar">
      <div class="time" id="clock"></div>
    </div>

    Here's #3. This time, the width isn't completely arbitrary, it's 1ch, which is the width of the 0 in the current font (details):

    function startTime() {
        var today = new Date();
        var h = today.getHours();
        var m = today.getMinutes();
        var s = today.getSeconds();
        var y = today.getFullYear();
        var o = today.getMonth();
        var a = today.getDate();
        var d = today.getDay();
    
        var monthnames = new Array();
        monthnames[0] = "January";
        monthnames[1] = "February";
        monthnames[2] = "March";
        monthnames[3] = "April";
        monthnames[4] = "May";
        monthnames[5] = "June";
        monthnames[6] = "July";
        monthnames[7] = "August";
        monthnames[8] = "September";
        monthnames[9] = "Oktober";
        monthnames[10] = "November";
        monthnames[11] = "December";
    
        var weekdays = new Array();
        weekdays[0] = "Sunday";
        weekdays[1] = "Monday";
        weekdays[2] = "Tuesday";
        weekdays[3] = "Wednesday";
        weekdays[4] = "Thursday";
        weekdays[5] = "Friday";
        weekdays[6] = "Saturday";
    
        m = checkTime(m);
        s = checkTime(s);
        a = checkTime(a);
    
    
        document.getElementById('clock').innerHTML =
            `<h1>${weekdays[d]}, ${a} ${monthnames[o]}, ${y}, <span id="clocktime">${clockDigits(h)}${clockDigits(":")}${clockDigits(m)}${clockDigits(":")}${clockDigits(s)}</span></h1>`;
        var t = setTimeout(startTime, 500);
    }
    
    function checkTime(i) {
        if (i < 10) {
            return "0" + i;  // Fügt vor einstelligen Zahlen (i > 10) eine Null hinzu
        }
        return String(i);
    }
    
    function clockDigits(s) {
        return String(s).split("").map(ch => `<span class="digit">${ch}</span>`).join("");
    }
    
    startTime()
    @import url('https://fonts.googleapis.com/css?family=Ropa+Sans');
    * {
      font-family: Ropa Sans;
      margin: 0;
    }
    
    .top-bar {
      width: 100%;
      height: 50px;
      background-color: #ccc;
      display: flex;
      justify-content: flex-end;
    }
    
    .time {
      margin-top: auto;
      margin-bottom: auto;
      margin-right: 1em;
    }
    #clocktime .digit {
      display: inline-block;
      width: 1ch;
      text-align: center;
    }
    <div>x</div>
    <div>x</div>
    <div>x</div>
    <div class="top-bar">
      <div class="time" id="clock"></div>
    </div>

    (Note that in that third one I also made checkTime reliably return a string instead of sometimes returning a string and other times return a number.)