Как замаскировать объект при прохождении участка пути за другим объектом?

При циклическом движении персонажа (в данном случае снеговика) он должен исчезать на участке трассы, проходящей за ёлкой.

введите сюда описание изображения

С помощью команды анимации движения: 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 шт):

Автор решения: De.Minov

В голову пришла только реализация с использованием маски.

Нужно создать маску по форме статичного объекта:

введите сюда описание изображения Я просто путь набросал. В идеале делать красивее :)

Применяем маску на объект, который движется.
Но есть вероятность столкнуться с проблемой, что когда движущийся объект проходит "перед" статичным, то маска обрезает движущийся объект.

С такой проблемой я и столкнулся:

введите сюда описание изображения

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>

Собственно на этом всё.

Важно, это не универсальный способ решения.
В условиях этого вопроса, это решение оказалось относительно простым, в других же случаях, либо больше кода будет, либо вообще вариант не подходящий.

→ Ссылка
Автор решения: Stanislav Volodarskiy

Красный круг, зелёный квадрат, красный круг. Один красный круг рисуется под квадратом, второй поверх квадрата. Круги двигаются синхронно, у них анимирована прозрачность так чтобы в сумме два круга всегда были непрозрачны. Возникает иллюзия что это один круг, который пролетает то под квадратом, то над ним:

<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. Моё решение не подходит для вопроса. В вопросе ёлка и фон – единая картинка. Моё решение требует чтобы ёлка стала отдельным объектом. Её в любом случае надо как-то отделить от фона. Это отделение или будет маской или ёлку можно вырезать из фона. Трудоёмкость не отличается: имея маску мы уже фактически готовы вырезать ёлку. А вот качество с вырезанной ёлкой можно получить лучшее за счёт обработки контура и полупрозрачности самой ёлки.

→ Ссылка
Автор решения: puffleeck

пример программного клепания масок "на лету", посредством набора фильтров а-ля найти грани, о чем и говорил в коментах.

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. деревце стало слегка прозрачное. видимо из-за того, что в маске цвета не стого черный и белый как должны быть, но кмк так даже интересней

еще из недостатков конечно же то там то тут не очень внятно обрезанные куски изображения(например слева снизу у ёлки "хвост" поверх снеговика болтается).

но если обрезать снег до обработки фильтрами(скажем канвасом), большей части хвостов конечно же не останется

→ Ссылка