즉시 고지 사항 - 이미지 프레임에 애니메이션을 적용하는 것이 아니라 이미지 뒤에 있는 배경에 애니메이션을 적용하고 패딩을 추가하는 것입니다.
저는 제 프로젝트 중 하나에 업로드된 이미지 그리드에 있는 몇몇 이미지에 더 많은 관심을 끌고 싶었습니다. 무난한 카드 디자인을 하거나 이미지 주변의 테두리와 그림자를 수정하지 않고 이 기술을 고안해냈습니다.
이 아이디어는 메인 이미지 색상을 추출하고 그 사이의 전환을 통해 그라데이션(gradient)을 구성하는 것입니다. 이 그라데이션은 이미지를 "원 둘레"로 만들어 더욱 생동감 있고 활기차게 보이게 합니다.
동시에, 각 이미지는 고유한 애니메이션 그라데이션이 있을 것이며, 이는 이미지, 이미지 프레임, 용기 배경색 간의 전환을 더욱 원활하게 만들어 줄 것이라고 생각합니다.
이를 위해 이미지마다 메인 이미지 색상을 추출한다. 이 색의 "팔레트"는 앞서 언급한 그라데이션의 구성에 사용된다.
몇 가지 예:
이 코드는 케플러 겔로트의 영향을 많이 받았다.
이미지 색 추출
입력 매개 변수는 이미지의 경로, 추출할 색상 수 및 델타 - 색상 값을 정량화할 때의 간격(1-255)입니다. 델타가 작을수록 색상이 정확하지만 유사한 색상의 수도 증가합니다.
<?php
declare(strict_types=1);
namespace App\Service;
class ColorPaletteExtractor
{
private const PREVIEW_WIDTH = 250;
private const PREVIEW_HEIGHT = 250;
public function extractMainImgColors(string $imagePath, int $colorsCount, int $delta): array
{
$halfDelta = 0;
if ($delta > 2) {
$halfDelta = $delta / 2 - 1;
}
$size = getimagesize($imagePath);
$scale = 1;
if ($size[0] > 0) {
$scale = min(self::PREVIEW_WIDTH / $size[0], self::PREVIEW_HEIGHT / $size[1]);
}
$width = (int) $size[0];
$height = (int) $size[1];
if ($scale < 1) {
$width = (int) floor($scale * $size[0]);
$height = (int) floor($scale * $size[1]);
}
$imageResized = imagecreatetruecolor($width, $height);
$imageType = $size[2];
$imageOriginal = null;
if (IMG_JPEG === $imageType) {
$imageOriginal = imagecreatefromjpeg($imagePath);
}
if (IMG_GIF === $imageType) {
$imageOriginal = imagecreatefromgif($imagePath);
}
if (IMG_PNG === $imageType) {
$imageOriginal = imagecreatefrompng($imagePath);
}
imagecopyresampled($imageResized, $imageOriginal, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);
$img = $imageResized;
$imgWidth = imagesx($img);
$imgHeight = imagesy($img);
$totalPixelCount = 0;
$hexArray = [];
for ($y = 0; $y < $imgHeight; $y++) {
for ($x = 0; $x < $imgWidth; $x++) {
$totalPixelCount++;
$index = imagecolorat($img, $x, $y);
$colors = imagecolorsforindex($img, $index);
if ($delta > 1) {
$colors['red'] = intval((($colors['red']) + $halfDelta) / $delta) * $delta;
$colors['green'] = intval((($colors['green']) + $halfDelta) / $delta) * $delta;
$colors['blue'] = intval((($colors['blue']) + $halfDelta) / $delta) * $delta;
if ($colors['red'] >= 256) {
$colors['red'] = 255;
}
if ($colors['green'] >= 256) {
$colors['green'] = 255;
}
if ($colors['blue'] >= 256) {
$colors['blue'] = 255;
}
}
$hex = substr('0' . dechex($colors['red']), -2)
. substr('0' . dechex($colors['green']), -2)
. substr('0' . dechex($colors['blue']), -2);
if (!isset($hexArray[$hex])) {
$hexArray[$hex] = 0;
}
$hexArray[$hex]++;
}
}
// Reduce gradient colors
arsort($hexArray, SORT_NUMERIC);
$gradients = [];
foreach ($hexArray as $hex => $num) {
if (!isset($gradients[$hex])) {
$newHexValue = $this->findAdjacent((string) $hex, $gradients, $delta);
$gradients[$hex] = $newHexValue;
} else {
$newHexValue = $gradients[$hex];
}
if ($hex != $newHexValue) {
$hexArray[$hex] = 0;
$hexArray[$newHexValue] += $num;
}
}
// Reduce brightness variations
arsort($hexArray, SORT_NUMERIC);
$brightness = [];
foreach ($hexArray as $hex => $num) {
if (!isset($brightness[$hex])) {
$newHexValue = $this->normalize((string) $hex, $brightness, $delta);
$brightness[$hex] = $newHexValue;
} else {
$newHexValue = $brightness[$hex];
}
if ($hex != $newHexValue) {
$hexArray[$hex] = 0;
$hexArray[$newHexValue] += $num;
}
}
arsort($hexArray, SORT_NUMERIC);
// convert counts to percentages
foreach ($hexArray as $key => $value) {
$hexArray[$key] = (float) $value / $totalPixelCount;
}
if ($colorsCount > 0) {
return array_slice($hexArray, 0, $colorsCount, true);
} else {
return $hexArray;
}
}
private function normalize(string $hex, array $hexArray, int $delta): string
{
$lowest = 255;
$highest = 0;
$colors['red'] = hexdec(substr($hex, 0, 2));
$colors['green'] = hexdec(substr($hex, 2, 2));
$colors['blue'] = hexdec(substr($hex, 4, 2));
if ($colors['red'] < $lowest) {
$lowest = $colors['red'];
}
if ($colors['green'] < $lowest) {
$lowest = $colors['green'];
}
if ($colors['blue'] < $lowest) {
$lowest = $colors['blue'];
}
if ($colors['red'] > $highest) {
$highest = $colors['red'];
}
if ($colors['green'] > $highest) {
$highest = $colors['green'];
}
if ($colors['blue'] > $highest) {
$highest = $colors['blue'];
}
// Do not normalize white, black, or shades of grey unless low delta
if ($lowest == $highest) {
if ($delta > 32) {
return $hex;
}
if ($lowest == 0 || $highest >= (255 - $delta)) {
return $hex;
}
}
for (; $highest < 256; $lowest += $delta, $highest += $delta) {
$newHexValue = substr('0' . dechex($colors['red'] - $lowest), -2)
. substr('0' . dechex($colors['green'] - $lowest), -2)
. substr('0' . dechex($colors['blue'] - $lowest), -2);
if (isset($hexArray[$newHexValue])) {
// same color, different brightness - use it instead
return $newHexValue;
}
}
return $hex;
}
private function findAdjacent(string $hex, array $gradients, int $delta)
{
$red = hexdec(substr($hex, 0, 2));
$green = hexdec(substr($hex, 2, 2));
$blue = hexdec(substr($hex, 4, 2));
if ($red > $delta) {
$newHexValue = substr('0' . dechex($red - $delta), -2)
. substr('0' . dechex($green), -2)
. substr('0' . dechex($blue), -2);
if (isset($gradients[$newHexValue])) {
return $gradients[$newHexValue];
}
}
if ($green > $delta) {
$newHexValue = substr('0' . dechex($red), -2)
. substr('0' . dechex($green - $delta), -2)
. substr('0' . dechex($blue), -2);
if (isset($gradients[$newHexValue])) {
return $gradients[$newHexValue];
}
}
if ($blue > $delta) {
$newHexValue = substr('0' . dechex($red), -2)
. substr('0' . dechex($green), -2)
. substr('0' . dechex($blue - $delta), -2);
if (isset($gradients[$newHexValue])) {
return $gradients[$newHexValue];
}
}
if ($red < (255 - $delta)) {
$newHexValue = substr('0' . dechex($red + $delta), -2)
. substr('0' . dechex($green), -2)
. substr('0' . dechex($blue), -2);
if (isset($gradients[$newHexValue])) {
return $gradients[$newHexValue];
}
}
if ($green < (255 - $delta)) {
$newHexValue = substr('0' . dechex($red), -2)
. substr('0' . dechex($green + $delta), -2)
. substr('0' . dechex($blue), -2);
if (isset($gradients[$newHexValue])) {
return $gradients[$newHexValue];
}
}
if ($blue < (255 - $delta)) {
$newHexValue = substr('0' . dechex($red), -2)
. substr('0' . dechex($green), -2)
. substr('0' . dechex($blue + $delta), -2);
if (isset($gradients[$newHexValue])) {
return $gradients[$newHexValue];
}
}
return $hex;
}
}
6
10가지 색상과 20
35가지 델타 작업을 했을 때 가장 좋은 결과를 얻고 있었습니다.
CSS는 매우 간단합니다.
.gradient-background-animation {
background-size: 400% 400% !important;
animation: BackgroundGradient 10s ease infinite;
}
.card-picture-container {
padding: 0.875rem; // This value determines how big the "frame" will be
}
.card-picture-container img {
object-fit: contain;
max-width: 100%;
}
@keyframes BackgroundGradient {
0% {
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
레이아웃과 스타일링을 원하는 대로 조정할 수 있습니다.
HTML:
<div class="container">
<div class="card-picture-container gradient-background-animation"
style="background-image: linear-gradient(-45deg, #78785a,#5a783c,#3c5a1e,#969678,#b4b4b4,#1e1e1e,#d2d2f0)">
<picture>
<img src="/images/source/image.jpeg" alt="">
</picture>
</div>
</div>
보시다시피 백그라운드는 인라인 CSS로 추가됩니다.
최종 결과(미리보기 목적으로 크기가 감소):
Twig 확장(필터)을 만들어 Symfony 프로젝트에 포함시켰습니다.
<?php
declare(strict_types=1);
namespace App\Twig;
use App\Service\ColorPaletteExtractor;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class GradientExtractorExtension extends AbstractExtension
{
public function __construct(
private ColorPaletteExtractor $gradientExtractor,
) {
}
public function getFilters(): array
{
return [
new TwigFilter('get_gradient', [$this, 'getGradient']),
];
}
public function getGradient(string $imagePath, int $colorsCount, int $delta): string
{
$colors = $this->gradientExtractor->extractMainImgColors($imagePath, $colorsCount, $delta);
$filteredColors = array_filter($colors, fn (float $percentage) => $percentage > 0);
return join(',', array_map(fn (string $color) => '#' . $color, array_keys($filteredColors)));
}
}
image-grid.dig.tig
<div class="container">
{ for image in images }
<div class="card-picture-container gradient-background-animation"
style="background-image: linear-gradient(-45deg, { asset(image)|get_gradient(7,30) }); animation-duration: 8s;">
<picture>
<img src="{ asset(image) }" alt="">
</picture>
</div>
{ endfor }
</div>
https://www.parkovihrvatske.hr/parkovi에서 다운로드한 테스트용 사진
'css' 카테고리의 다른 글
CSS 프레임워크 소개(부트스트랩) (0) | 2022.02.22 |
---|---|
CSS에서 논리 쓰기 (0) | 2022.02.22 |
jarallax.js로 아름다운 시차 랜딩 페이지를 만드는 방법 (0) | 2022.02.22 |
무한 루프 React 구성 요소 (0) | 2022.02.22 |
https 집에서 시도하지 마십시오: CSS _as_ 백엔드 - Cascading Server Sheet 소개! (0) | 2022.02.22 |
댓글