hteumeuleu / email-bugs

Email quirks and bugs
537 stars 20 forks source link

Gmail and display:inline-block #80

Open hteumeuleu opened 4 years ago

hteumeuleu commented 4 years ago

Since a week or so, I'm starting to see issues in Gmail when using display:inline-block to set columns side by side. It's as if the space between two elements would be forced there (even though I use font-size:0 on a parent element to remove it.

A solution I found is to use HTML comments between the two elements to eliminate the white space.

Here's what it looks like in my iPhone SE (first gen):

Example email showing two columns failing behind each others

Here's the code with two examples, the first one breaking and the second one (with the comments fix) :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Gmail display:inline-block bug</title>
</head>
<body>
    <div style="font-size:0; text-align:center;">
        <div style="display:inline-block; width:50%; font-size:16px; background:orange; height:120px;">
            A
        </div>
        <div style="display:inline-block; width:50%; font-size:16px; background:orange; height:120px;">
            B
        </div>
    </div>
    <div style="font-size:0; text-align:center;">
        <div style="display:inline-block; width:50%; font-size:16px; background:green; height:120px;">
            A
        </div><!--
        --><div style="display:inline-block; width:50%; font-size:16px; background:green; height:120px;">
            B
        </div>
    </div>
</body>
</html>

But I've found this to be very inconsistent. It doesn't happen on all my devices. And it doesn't happen on all accounts in Gmail. Can anyone else test the above code and share what your results are?

M-J-Robbins commented 4 years ago

Just had a quick look into this. Gmail on iOS adds word-spacing: 1px; to the body. If you add word-spacing:normal it should fix it.

This is what Gmail adds

body {
  -webkit-text-size-adjust: none;
  background-color: #ffffff;
  color: #313131;
  font-family: Roboto;
  font-size: 16px;
  line-height: 1.4;
  margin: 0;
  margin-right: 15px;
  padding: 0;
  word-spacing: 1px;
  word-wrap: break-word;
  min-height: 10px;
}
hteumeuleu commented 4 years ago

Wow, nice! This works perfectly. (By adding <body style="word-spacing:normal"> to my previous example.) Out of curiousity, how did you get those default styles on the body? Using a proxy?

M-J-Robbins commented 4 years ago

I got it from the Litmus processed HTML view.

Here's the full processed HTMl from your code above.

<head>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <script type="text/javascript" src="termhighlighter.bin"></script>
    <script>gbt = {};gbt.documentContainerDivId = 'gbt_container';gbt.doubleTapToZoom = false;</script>
    <style type="text/css">
        body {
  -webkit-text-size-adjust: none;
  background-color: #ffffff;
  color: #313131;
  font-family: Roboto;
  font-size: 16px;
  line-height: 1.4;
  margin: 0;
  margin-right: 15px;
  padding: 0;
  word-spacing: 1px;
  word-wrap: break-word;
  min-height: 10px;
}

.term-highlighted {
  background-color: rgba(251, 246, 167, 0.5);
}

.gbtc {
  color:#757575 !important;
}

pre {
  white-space: pre-wrap;
}

    </style>
    <style type="text/css">

    </style>
</head>
<body class="(null)">
    <div id="gbt_container" style="float: left; transform-origin: 0px 0px; transform: translate3d(0px, 0px, 0px) scale3d(1, 1, 1);"><div style="margin: 0px 16px;" class=""><u></u>

<div>
    <div style="font-size:0;text-align:center">
        <div style="display:inline-block;width:50%;font-size:16px;background:orange;height:120px">
            A
        </div>
        <div style="display:inline-block;width:50%;font-size:16px;background:orange;height:120px">
            B
        </div>
    </div>
    <div style="font-size:0;text-align:center">
        <div style="display:inline-block;width:50%;font-size:16px;background:green;height:120px">
            A
        </div><div style="display:inline-block;width:50%;font-size:16px;background:green;height:120px">
            B
        </div>
    </div>
</div>

</div></div>

<script type="text/javascript">
    /** @type {Object} */
var gbt;

/** @type {number} The x transform offset used when gbt.update is called. */
var gbtX;
/** @type {number} The y transform offset used when gbt.update is called. */
var gbtY;
/** @type {number} The scale transform used when gbt.update is called. */
var gbtScale;

/** @type {number} Millis since last touchend event for double tap to zoom. */
gbt.lastTouchEnd = 0;
/** @type {number} Maximum ms between taps for a double tap. */
gbt.doubleTapCutoff = 500;
/** @type {number} Current number of touches in progress. */
gbt.touchCount = 0;
/** @type {string} The name of the container div. */
gbt.documentContainerDivId;
/** @type {boolean} If double-tap to zoom should be enabled. */
gbt.doubleTapToZoom;

/**
 * Updates the css transform to reflect the current gbt(X|Y|Scale) state.
 * @param {boolean} animated Whether the update should animate from the current
 *     state.
 */
gbt.update = function (animated) {
    gbt.lastTouchEnd = 0;
    var body = document.getElementById(gbt.documentContainerDivId);
    if (!body) {
        return;
    }
    var x = gbtX;
    var y = gbtY;
    var scale = gbtScale;
    var update = function () {
        body.style.webkitTransformOrigin = '0 0';
        body.style.webkitTransform = 'translate3d(-' + x + 'px,-' + y +
            'px,0px) scale3d(' + scale + ',' + scale + ',1)';
    };
    if (animated) {
        var duration = 200;
        body.style.webkitTransition =
            '-webkit-transform ' + duration + 'ms ease-in';
        window.setTimeout(function () {
            update();
            window.setTimeout(function () {
                body.style.webkitTransition = 'none';
            }, duration);
        }, 0);
    } else {
        update();
    }
    return;
};

/**
 * Serializes a rectangle to CGRectFromNSString-format.
 * @param {!ClientRect} rect .
 * @return {string} The serialized string.
 */
gbt.serializeRect = function (rect) {
    return '{{' + rect.left + ', ' + rect.top + '}, {' + rect.width + ', ' +
        rect.height + '}}';
};

/**
 * Returns, as a string, the bounding rectangle of the focused element, or the
 * empty string if there is no focused element.
 * @return {string} The bounding rectangle.
 */
gbt.focusRect = function () {
    if (document.body && document.activeElement != document.body) {
        var rect = document.activeElement.getBoundingClientRect();
        if (rect) {
            return gbt.serializeRect(rect);
        }
    }
    return '';
};

/**
 * Sends a message to the app.
 * @param {string} data The message to send.
 */
gbt.rpc = function (data) {
    var iframe = document.createElement('iframe');
    iframe.setAttribute('src', 'gbt://#' + data);
    document.documentElement.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;
};

/**
 * Sends a messages to the app with the given command and the bounding rect for
 * the given DOM element.
 * @param {string} command The command ID to send.
 * @param {Object} target The target DOM element.
 */
gbt.rpcWithRectForTarget = function (command, target) {
    var rect = target.getBoundingClientRect();
    gbt.rpc('rpc=' + command + '&left=' + rect.left + '&width=' + rect.width +
            '&top=' + rect.top + '&height=' + rect.height);
};

if (gbt.doubleTapToZoom) {
    document.body.addEventListener('touchstart', function (e) {
        gbt.touchCount += e.changedTouches.length;
    });
    document.body.addEventListener('touchcancel', function (e) {
        e.preventDefault();
        gbt.touchCount -= e.changedTouches.length;
    });
    document.body.addEventListener('touchend', function (e) {
        e.preventDefault();
        gbt.touchCount -= e.changedTouches.length;
        if (gbt.touchCount > 0) {
            return;
        }
        var now = new Date().getTime();
        if (now - gbt.lastTouchEnd < gbt.doubleTapCutoff) {
            // Zoom
            gbt.rpcWithRectForTarget('zoom', e.target);
            gbt.lastTouchEnd = 0;
        } else {
            gbt.lastTouchEnd = now;
        }
    }, false);
}

document.body.addEventListener('focusin', function (e) {
    gbt.rpcWithRectForTarget('focusin', e.target);
});

if (typeof gbtX !== undefined && typeof gbtY !== undefined &&
    typeof gbtScale !== undefined) {
    gbt.update(false);
}

</script>
</body>
hteumeuleu commented 4 years ago

Ha, nice! I didn't know this worked for mobile apps as well.

pbiolsi commented 4 years ago

Sounds like a valuable addition to the gmail resets! I forget, is there a good resource out there with a current list of tested/trusted resets?

Could be a nice addition to this repo…

JayOram commented 3 months ago

@pbiolsi - I know this is quite old - but this repo has all the needed CSS resets/normalise attributes for email: https://github.com/JayOram/email-css-resets