How to simulate delay() to debounce mechanical button using millis()?
To better understand how you can do this it is good to understand first just what goes on when a button bounces.

It is, literally, bouncing - just like a ball when you drop it.  Due to the fact that it's either connected to ground or pulled up to +5V by the pullup resistor it's either going to be LOW or HIGH with very little time between when it's not in either state, so we can pretty much ignore the rise and fall times of the signal.  It's either connected to ground, or it's not connected to ground.  And we're only really interested in when it's connected to ground.

More importantly we're interested in how long it's been connected to ground for.

By remembering the time it transitioned from HIGH to LOW and then comparing that time to the current time you can know how long it has been connected to ground.  By noticing when it's no longer connected to ground and "forgetting" the time you remembered you can start counting afresh with the next transition from HIGH to LOW.

Take the following diagram for instance:



The blue line is the state of the button.  As you can see it starts HIGH, then it gets pressed to be LOW and bounces between HIGH and LOW a number of times before it settles on being LOW.

The red line is the difference between a timestamp that is set to be equal to millis() at the moment the button transitions from HIGH to LOW and the current millis() value, and is ignored when the button is HIGH.  As you can see the difference between that timestamp and the current millis() value steadily increases. Whenever the button bounces to the HIGH state it resets that difference, and it starts again with the next LOW transition.

When the button has finished bouncing there is nothing to change the state of the timestamp, so the difference keeps increasing.  Eventually that difference will pass a trigger point (the yellow line in this diagram) and at that point you can be fairly sure that the button has finished bouncing and you should react to it.

To translate that into some code, you may end up with something like this (untested):

// Two variables to store remembered state information - the time
// of the last HIGH-LOW transition and the previous state of the
//  button.
static uint32_t debounceTime = 0;
static uint8_t buttonState = HIGH;

// Check to see if the button has changed state
if (digitalRead(buttonPin) != buttonState) {
    // Remember the new state
    buttonState = digitalRead(buttonPin);
    // If the new state is LOW ...
    if (buttonState == LOW) {
        // ... then record the time stamp
        debounceTime = millis();
    } else {
        // ... otherwise set the time stamp to 0 for "don't care".
        debounceTime = 0;
    }
}

// If the time stamp is more than 0 and the difference between now and
// then is greater than 10ms ...
if (debounceTime &gt; 0 &amp;&amp; (millis() - debounceTime &gt; 10)) {
    // ... set the time stamp to 0 to say we have finished debouncing
    debounceTime = 0;
    // and do whatever it is you need to do.
    digitalWrite(ledPin, HIGH);
}


There are various tweaks and additions that could be made to that method, but that gives you the basic idea of how you split the debouncing away from the reacting, and it's the results of the debouncing that you react to, not the button state itself.