Skip to content

Commit

Permalink
helpers\ImageLazifier: set placeholder src
Browse files Browse the repository at this point in the history
With lazy loading of images enabled, `img` tags did not have a `src` attribute, causing the browsers to render them as zero-height boxes. This made it impossible to determine the precise height of the article content, leading to articles sometimes being rendered in column view, even though they would not fit a single screen with images loaded.

In Firefox, it is possible to use broken non-image (e.g. about:blank page) and it will render a rectangle respecting the width and height ratio, there must be no alt attribute, though. And Chromium will not respect the aspect ratio either.

https://jsfiddle.net/jtojnar/yrdskx0n/5/

It is also possible to use a real image (e.g. 1×1 px dot) but then the `height: auto` in CSS causes the images to respect the size of the placeholder, not the specified dimensions. For that reason we need to use a placeholder of the same dimensions as the original image.

This will not help for images that do not specify dimensions but then again, we cannot know their dimension until they are loaded anyway, so let’s just fall back to 800×600 as a guess.
  • Loading branch information
jtojnar committed Jun 21, 2020
1 parent c24cc38 commit c4299fb
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 1 deletion.
27 changes: 26 additions & 1 deletion src/helpers/ViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,32 @@ public static function highlight($content, $searchWords) {
* @return string with replaced img tags
*/
public static function lazyimg($content) {
return preg_replace("/<img([^<]+)src=(['\"])([^\"']*)(['\"])([^<]*)>/i", "<img$1ref='$3'$5>", $content);
return preg_replace_callback("/<img(?P<pre>[^<]+)src=(?:['\"])(?P<src>[^\"']*)(?:['\"])(?P<post>[^<]*)>/i", function($matches) {
$width = null;
$height = null;

$attrs = "{$matches['pre']} {$matches['post']}";
if (preg_match('/\bwidth=([\'"]?)(?P<width>\d+)\1/i', $attrs, $widthAttr)) {
$width = $widthAttr['width'];
}
if (preg_match('/\bheight=([\'"]?)(?P<height>\d+)\1/i', $attrs, $heightAttr)) {
$height = $heightAttr['height'];
}

if ($width === null && $height === null) {
// no dimensions provided, assume a 4:3 photo
$width = 800;
$height = 600;
} else {
// assume a 4:3 photo
$width = $width === null ? $height * 4 / 3 : $width;
$height = $height === null ? $width * 3 / 4 : $height;
}

$placeholder = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='$width' height='$height'><rect fill='%2395c9c5' width='100%' height='100%'/></svg>";

return "<img src=\"$placeholder\"{$matches['pre']}ref=\"{$matches['src']}\"{$matches['post']}>";
}, $content);
}

/**
Expand Down
76 changes: 76 additions & 0 deletions tests/Helpers/ImageLazifierTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace Tests\Helpers;

use helpers\ViewHelper;
use PHPUnit\Framework\TestCase;

final class ImageLazifierTest extends TestCase {
/**
* Check that src attribute is renamed, other attributes are preserved and a properly-sized placeholder is chosen.
*/
public function testBasic() {
$input = <<<EOD
<img foo bar src="https://example.org/example.jpg" alt="" width="900" height="400">
EOD;
$expected = <<<EOD
<img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='900' height='400'><rect fill='%2395c9c5' width='100%' height='100%'/></svg>" foo bar ref="https://example.org/example.jpg" alt="" width="900" height="400">
EOD;

$this->assertEquals(
$expected,
ViewHelper::lazyimg($input)
);
}

/**
* Check that width for the placeholder is calculated from height.
*/
public function testWidthMissing() {
$input = <<<EOD
<img src="https://example.org/example.jpg" height="300">
EOD;
$expected = <<<EOD
<img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='300'><rect fill='%2395c9c5' width='100%' height='100%'/></svg>" ref="https://example.org/example.jpg" height="300">
EOD;

$this->assertEquals(
$expected,
ViewHelper::lazyimg($input)
);
}

/**
* Check that height for the placeholder is calculated from width.
*/
public function testHeightMissing() {
$input = <<<EOD
<img src="https://example.org/example.jpg" width="400">
EOD;
$expected = <<<EOD
<img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='300'><rect fill='%2395c9c5' width='100%' height='100%'/></svg>" ref="https://example.org/example.jpg" width="400">
EOD;

$this->assertEquals(
$expected,
ViewHelper::lazyimg($input)
);
}

/**
* Check that placeholder dimensions are chosen even when the image does not specify any.
*/
public function testDimensionsMissing() {
$input = <<<EOD
<img src="https://example.org/example.jpg">
EOD;
$expected = <<<EOD
<img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='800' height='600'><rect fill='%2395c9c5' width='100%' height='100%'/></svg>" ref="https://example.org/example.jpg">
EOD;

$this->assertEquals(
$expected,
ViewHelper::lazyimg($input)
);
}
}

0 comments on commit c4299fb

Please sign in to comment.