Как замаскировать объект при прохождении участка пути за другим объектом?
При циклическом движении персонажа (в данном случае снеговика) он должен исчезать на участке трассы, проходящей за ёлкой.
С помощью команды анимации движения: animateMotion реализовано движения снеговика по заданному пути path id="trace"
Движение начинается после клика
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1680 1050" preserveAspectRatio="xMinYMin meet">
<!-- Фон -->
<image xlink:href="https://i.sstatic.net/PBRad.jpg" width="1680" height="1050"/>
<!-- Трасса движения -->
<path id="trace" style="fill:none;stroke:#e20000;stroke-width:2" d="M1297.7 862.8c-58.4-49.6-136 24.8-251.6-4.7-18.5-4.7-56.8-38.5-88.1-52a394.6 394.6 0 0 0-77.1-22c-20.7-4.2-42.1-11.4-63-7.8-24.2 4.2-45.2 19.9-66 33-18 11.4-30.2 32.3-50.3 39.4-38.1 13.3-102.2-34.1-121.2 1.5-7.6 14.4 10.5 39.8 26.8 41 258 18 411.5 14.2 649.6 0 16.6-1 53.6-17.7 41-28.4z"/>
<!-- Снеговик -->
<image id="Snowman" x="-100" y="-340" transform="scale(0.4)" xlink:href="https://i.sstatic.net/mbefD.png" opacity="1">
</image>
<animateMotion xlink:href="#Snowman"
begin="svg1.click"
dur="10s"
calcMode="linear"
repeatDur="indefinite" >
<mpath xlink:href="#trace"/>
</animateMotion>
</svg>
Как замаскировать снеговика при прохождении участка трассы за ёлкой?
Ответ может быть дан по любой метке из перечня меток, но предпочтение ответам по меткам CSS, SVG.
Update 17.01.2025
Уже есть один отличный ответ, но судя по комментариям есть много идей, как решить этот вопрос другими способами. Друзья размещайте ответы, переходите от слов к делу!
Победа в конкурсе будет присуждена ответу, в котором наиболее полно выполнены условия конкурса.
Красота и оригинальность ответа. Это конечно субъективно, но кто платит, тот и заказывает музыку.
Наибольшее число Upvote
Поздравляем победителя конкурса De.Minov!
Ответы (3 шт):
В голову пришла только реализация с использованием маски.
Нужно создать маску по форме статичного объекта:
Я просто путь набросал. В идеале делать красивее :)
Применяем маску на объект, который движется.
Но есть вероятность столкнуться с проблемой, что когда движущийся объект проходит "перед" статичным, то маска обрезает движущийся объект.
С такой проблемой я и столкнулся:
body { height: 100vh; overflow: hidden; margin: 0; background-image: linear-gradient(to top, #446085, #06060e 30%); }
svg { display: block; width: 100%; height: 100%; }
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="1680" height="1050" viewBox="0 0 1680 1050">
<defs>
<mask id="maskBehindTree" maskUnits="userSpaceOnUse">
<!-- Всё видимое поле, белое -->
<rect x="0" y="0" width="1680" height="1050" fill="#fff" />
<!-- Фигура, где хотим "прятать" снеговика (там чёрный) -->
<path
d="M831 856C836 848 837 840 828 833 791 834 733 805 723 792 709 767 716 737 717 713 719 662 725.6667 633.6667 730 594 741 505 958 507 971 594 975 650 984 752 967 780 953 812 897 844 870 829 864 834 862 845 870 856 861 858 840 859 831 856z"
fill="#000"
/>
</mask>
</defs>
<!-- Фон -->
<image
href="https://i.imgur.com/DUQ7JM8.png"
width="1680"
height="1050"
/>
<!-- Трасса движения -->
<path
id="trace"
fill="none"
stroke="#e20000"
stroke-width="2"
d="M1297.7 862.8c-58.4-49.6-136 24.8-251.6-4.7-18.5-4.7-56.8-38.5-88.1-52a394.6 394.6 0 0 0-77.1-22c-20.7-4.2-42.1-11.4-63-7.8-24.2 4.2-45.2 19.9-66 33-18 11.4-30.2 32.3-50.3 39.4-38.1 13.3-102.2-34.1-121.2 1.5-7.6 14.4 10.5 39.8 26.8 41 258 18 411.5 14.2 649.6 0 16.6-1 53.6-17.7 41-28.4z"
/>
<g id="SnowmanGroup" mask="url(#maskBehindTree)">
<!-- Снеговик -->
<image
id="Snowman"
x="-100"
y="-340"
transform="scale(.4)"
href="https://i.imgur.com/adbDcz8.png"
opacity="1"
/>
<!-- Анимация движения снеговика -->
<animateMotion
href="#Snowman"
begin="svg1.click"
dur="10s"
calcMode="linear"
repeatDur="indefinite"
>
<mpath href="#trace" />
</animateMotion>
</g>
</svg>
Решение пришло сразу, SVG позволяет управлять атрибутами.
Есть возможность создать анимацию, которая будет "включать\выключать" значение в атрибуте.
То есть, можно сделать анимацию, где через keyTimes
атрибут определяем время когда у нас будет отображаться маска, а в какой нет.
В вопросе движение начинается с правой стороны, идёт влево, в этот момент движущийся объект находится за статичным. Т.е. в прогрессе от 0 до .5 - отображаем маску, а от .5 до 1, когда движущийся объект перед статичным — отключаем.
Для этого создаём следующую анимацию:
<animate
href="#SnowmanGroup" // обращаемся к группе со снеговиком
attributeName="mask" // менять будет атрибут mask
begin="svg1.click" // начало анимации как у основной анимации
dur="10s" // длина анимации тоже
repeatCount="indefinite"
fill="freeze"
values="url(#maskBehindTree);url(#maskBehindTree);none;none"
keyTimes="0;.49;.5;1" // в `values` указываем значение для маски
// и `none` - что означает отключение маски
// в `keyTimes` указываем прогресс, описанный выше
// т.е. от 0 до <.5 будет `mask="url(#maskBehindTree)"`
// а от .5 до 1 будет `mask="none"`
/>
body { height: 100vh; overflow: hidden; margin: 0; background-image: linear-gradient(to top, #446085, #06060e 30%); }
svg { display: block; width: 100%; height: 100%; }
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" width="1680" height="1050" viewBox="0 0 1680 1050">
<defs>
<mask id="maskBehindTree" maskUnits="userSpaceOnUse">
<!-- Всё видимое поле, белое -->
<rect x="0" y="0" width="1680" height="1050" fill="#fff" />
<!-- Фигура, где хотим "прятать" снеговика (там чёрный) -->
<path
d="M831 856C836 848 837 840 828 833 791 834 733 805 723 792 709 767 716 737 717 713 719 662 725.6667 633.6667 730 594 741 505 958 507 971 594 975 650 984 752 967 780 953 812 897 844 870 829 864 834 862 845 870 856 861 858 840 859 831 856z"
fill="#000"
/>
</mask>
</defs>
<!-- Фон -->
<image
href="https://i.imgur.com/DUQ7JM8.png"
width="1680"
height="1050"
/>
<!-- Трасса движения -->
<path
id="trace"
fill="none"
stroke="#e20000"
stroke-width="2"
d="M1297.7 862.8c-58.4-49.6-136 24.8-251.6-4.7-18.5-4.7-56.8-38.5-88.1-52a394.6 394.6 0 0 0-77.1-22c-20.7-4.2-42.1-11.4-63-7.8-24.2 4.2-45.2 19.9-66 33-18 11.4-30.2 32.3-50.3 39.4-38.1 13.3-102.2-34.1-121.2 1.5-7.6 14.4 10.5 39.8 26.8 41 258 18 411.5 14.2 649.6 0 16.6-1 53.6-17.7 41-28.4z"
/>
<g id="SnowmanGroup" mask="url(#maskBehindTree)">
<!-- Снеговик -->
<image
id="Snowman"
x="-100"
y="-340"
transform="scale(.4)"
href="https://i.imgur.com/adbDcz8.png"
opacity="1"
/>
<!-- Анимация движения снеговика -->
<animateMotion
href="#Snowman"
begin="svg1.click"
dur="10s"
calcMode="linear"
repeatDur="indefinite"
>
<mpath href="#trace" />
</animateMotion>
<!-- Анимация включения/отключения маски -->
<animate
href="#SnowmanGroup"
attributeName="mask"
begin="svg1.click"
dur="10s"
repeatCount="indefinite"
fill="freeze"
values="url(#maskBehindTree);url(#maskBehindTree);none;none"
keyTimes="0;.49;.5;1"
/>
</g>
</svg>
Собственно на этом всё.
Важно, это не универсальный способ решения.
В условиях этого вопроса, это решение оказалось относительно простым, в других же случаях, либо больше кода будет, либо вообще вариант не подходящий.
Красный круг, зелёный квадрат, красный круг. Один красный круг рисуется под квадратом, второй поверх квадрата. Круги двигаются синхронно, у них анимирована прозрачность так чтобы в сумме два круга всегда были непрозрачны. Возникает иллюзия что это один круг, который пролетает то под квадратом, то над ним:
<svg viewBox="0 0 200 100" width="400" height="200" xmlns="http://www.w3.org/2000/svg">
<circle r="10" fill="red">
<animateMotion
dur="5s"
repeatCount="indefinite"
path="M20,50 C20,-50 180,150 180,50 C180-50 20,150 20,50 z" />
<animate
attributeName="opacity"
values="1;0;0;0;1;1;1;1;1"
dur="5s"
repeatCount="indefinite" />
</circle>
<rect x="85" y="35" width="30" height="30" fill="green" />
<rect x="80" y="30" width="40" height="40" fill="green" opacity="50%" />
<circle r="10" fill="red">
<animateMotion
dur="5s"
repeatCount="indefinite"
path="M20,50 C20,-50 180,150 180,50 C180-50 20,150 20,50 z" />
<animate
attributeName="opacity"
values="1;1;1;1;1;0;0;0;0"
dur="5s"
repeatCount="indefinite" />
</circle>
</svg>
P.S. Моё решение не подходит для вопроса. В вопросе ёлка и фон – единая картинка. Моё решение требует чтобы ёлка стала отдельным объектом. Её в любом случае надо как-то отделить от фона. Это отделение или будет маской или ёлку можно вырезать из фона. Трудоёмкость не отличается: имея маску мы уже фактически готовы вырезать ёлку. А вот качество с вырезанной ёлкой можно получить лучшее за счёт обработки контура и полупрозрачности самой ёлки.
пример программного клепания масок "на лету", посредством набора фильтров а-ля найти грани
, о чем и говорил в коментах.
UPD 23.01.2025 / 4:05
для начала вырезаем маску на основе ёлки
#treeMask {
filter: blur(3px) brightness(1.5) contrast(20)
blur(5px) brightness(4) contrast(20)
blur(7px) contrast(25)
blur(10px) contrast(25)
blur(25px) contrast(50);
}
svg:hover #treeMask{
filter: blur(3px) brightness(1.5) contrast(20)
blur(5px) brightness(4) contrast(20)
blur(7px) contrast(25)
blur(10px) contrast(25)
blur(25px) contrast(50)
invert(100%);
}
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1680 1050" preserveAspectRatio="xMinYMin meet">
<mask id='mm'>
<image id='treeMask' xlink:href="https://i.sstatic.net/PBRad.jpg" width="1680" height="1050"/>
<rect x='0' y='825' width='1680' height='225' fill='white' opacity='.5'></rect>
</mask>
<image id='tree' xlink:href="https://i.sstatic.net/PBRad.jpg" width="1680" height="1050" mask='url(#mm)'/>
</svg>
после используем её точно так же как и в варианте от @De.Minov
тут отличие только в маске и ни в чем больше
#treeMask {
filter: blur(3px) brightness(1.5) contrast(20)
blur(5px) brightness(4) contrast(20)
blur(7px) contrast(25)
blur(10px) contrast(25)
blur(25px) contrast(50)
invert(100%);
}
#trace {
fill: none;
stroke: #e20000;
stroke-width: 2
}
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1680 1050" preserveAspectRatio="xMinYMin meet">
<mask id='mm'>
<image id='treeMask' xlink:href="https://i.sstatic.net/PBRad.jpg" width="1680" height="1050"/>
<rect x='0' y='825' width='1680' height='225' fill='white'></rect>
</mask>
<image id='bg' xlink:href="https://i.sstatic.net/PBRad.jpg" width="1680" height="1050"/>
<path id="trace" d="M1297.7 862.8c-58.4-49.6-136 24.8-251.6-4.7-18.5-4.7-56.8-38.5-88.1-52a394.6 394.6 0 0 0-77.1-22c-20.7-4.2-42.1-11.4-63-7.8-24.2 4.2-45.2 19.9-66 33-18 11.4-30.2 32.3-50.3 39.4-38.1 13.3-102.2-34.1-121.2 1.5-7.6 14.4 10.5 39.8 26.8 41 258 18 411.5 14.2 649.6 0 16.6-1 53.6-17.7 41-28.4z"/>
<g id="SnowmanGroup" mask="url(#mm)">
<!-- Снеговик -->
<image
id="Snowman"
x="-100"
y="-340"
transform="scale(.59)"
href="https://i.sstatic.net/mbefD.png"
opacity="1"
/>
<!-- Анимация движения снеговика -->
<animateMotion
href="#Snowman"
begin="svg1.click"
dur="10s"
calcMode="linear"
repeatDur="indefinite"
>
<mpath href="#trace" />
</animateMotion>
<!-- Анимация включения/отключения маски -->
<animate
href="#SnowmanGroup"
attributeName="mask"
begin="svg1.click"
dur="10s"
repeatCount="indefinite"
fill="freeze"
values="url(#mm);url(#mm);none;none"
keyTimes="0;.49;.7;1"
/>
</g>
</svg>
p.s. деревце стало слегка прозрачное. видимо из-за того, что в маске цвета не стого черный и белый как должны быть, но кмк так даже интересней
еще из недостатков конечно же то там то тут не очень внятно обрезанные куски изображения(например слева снизу у ёлки "хвост" поверх снеговика болтается).
но если обрезать снег до обработки фильтрами(скажем канвасом), большей части хвостов конечно же не останется