Переход к фильтру SVG

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

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

Теперь, когда у меня есть тени, я не могу найти способ их оживить. Я нашел несколько примеров использования тегов анимации для отдельных свойств (например, цвета круга) и нашел примеры использования ключевых кадров для переходов CSS, но здесь я хочу изменить сам фактический фильтр. Возможно ли это, и может ли кто-нибудь проиллюстрировать, как вы можете этого добиться - в идеале я пытаюсь добиться совместимости с IE10/FF/Chrome, поэтому мне было бы интересно узнать, есть ли какие-либо сложности с решением?

circle {
    fill: #8BC34A;
    stroke: white;
    stroke-width: 2px;
    filter: url(#f1);
    transition: 2s ease;
}

circle:hover {
    filter: url(#f2);
    transition: 2s ease;
}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500" viewPort="0 0 200 200">
  <defs>
    <filter id="f1" x="-40%" y="-40%" height="200%" width="200%">
      <feOffset result="offOut" in="SourceAlpha" dx="0" dy="0" />
      <feGaussianBlur result="blurOut" in="offOut" stdDeviation="10" />
      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
    </filter>
    <filter id="f2" x="-40%" y="-40%" height="200%" width="200%">
      <feOffset result="offOut" in="SourceAlpha" dx="0" dy="0" />
      <feGaussianBlur result="blurOut" in="offOut" stdDeviation="30" />
      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
    </filter>
  </defs>
  <circle r="100" cx="150" cy="150" />
</svg>

ОБНОВЛЕНИЕ

Попробовав несколько вещей, я собрал несколько примеров, хотя ни один из них не делает то, что я хочу. Мне нужно иметь возможность переключать переходы для одного или нескольких элементов (а не для каждого круга в SVG), для которых у меня может быть несколько сотен. Я также в конечном итоге хочу изменить размер круга (подняв его в соответствии с дизайном материала), а затем увеличить сторону тени под ним.

/*************************/
/* JavaScript Animations */
/*************************/
(function() { 
    var svg = d3.select("#svg_javaScriptAnimation");
    setInterval(function() {
        
        // Animate
        svg.selectAll(".circle")
           .transition()
           .duration(1950)
           .attr("r", 130);
        
        svg.selectAll(".jA_shadow")
           .transition()
           .duration(1950)
           .attr("r", 130);
        
         svg.selectAll(".jA_shadow_expanding")
           .transition()
           .duration(1950)
           .attr("r", 140);
        
        svg.selectAll(".jA_shadow_large")
           .transition()
           .duration(1950)
           .attr("r", 110);
        
        // Reset
         svg.selectAll(".circle")
           .transition()
           .delay(1960)
           .duration(1)
           .attr("r", 110);
        
         svg.selectAll(".jA_shadow")
           .transition()
           .delay(1960)
           .duration(1)
           .attr("r", 110);
        
         svg.selectAll(".jA_shadow_expanding")
           .transition()
           .delay(1960)
           .duration(1)
           .attr("r", 110);
        
        svg.selectAll(".jA_shadow_large")
           .transition()
           .delay(1960)
           .duration(1)
           .attr("r", 80);
    }, 2000);
})();
circle {
   fill: #8BC34A;
   stroke: white;
   stroke-width: 2px;
}

text {
    fill: white;
}

/*****************/
/* CSS KeyFrames */
/*****************/
#svg_keyframes{
  animation:filters 2s infinite;
}

@-webkit-keyframes filters {
  0%{ 
    -webkit-filter:drop-shadow(0px 16px 10px #333); 
  }
  100% { 
    -webkit-filter:drop-shadow(0px 16px 30px #333); 
  }
}

/***********************************/
/* CSS KeyFrames using SVG Filters */
/***********************************/

.kf_Shadow1 {
    -webkit-animation-name: shadow-expand; / Chrome, Safari, Opera /
    -webkit-animation-duration: 2s; / Chrome, Safari, Opera /
    -webkit-animation-iteration-count: infinite;
    animation-name: shadow-expand;
    animation-duration: 2s;
    animation-iteration-count: infinite;
}

.kf_Fill1 {
    -webkit-animation-name: circle-fill; / Chrome, Safari, Opera /
    -webkit-animation-duration: 2s; / Chrome, Safari, Opera /
    -webkit-animation-iteration-count: infinite;
    animation-name: circle-fill;
    animation-duration: 2s;
    animation-iteration-count: infinite;
}

.kf_DropShadow1 {
    -webkit-animation-name: drop-shadow-expand; / Chrome, Safari, Opera /
    -webkit-animation-duration: 2s; / Chrome, Safari, Opera /
    -webkit-animation-iteration-count: infinite;
    animation-name: drop-shadow-expand;
    animation-duration: 2s;
    animation-iteration-count: infinite;
}

/* Demonstrate that fill works correctly */
@keyframes circle-fill {
    0% { fill: #FF0000; }       
    25% { fill: #BB0033; }       
    50% { fill: #990066; }        
    75% { fill: #4400aa; }         
    100% { fill: #0000ff; }       
}

/* Demonstrate that filter doesn't work as hoped */
@keyframes shadow-expand {
    0% { filter: url(#f1); -webkit-filter: url(#f1);}       
    25% { filter: url(#f2); -webkit-filter: url(#f1);}       
    50% { filter: url(#f3); -webkit-filter: url(#f1);}       
    75% { filter: url(#f4); -webkit-filter: url(#f1);}       
    100% { filter: url(#f5); -webkit-filter: url(#f1);}       
}

@keyframes drop-shadow-expand {
    0% { -webkit-filter:drop-shadow(0px 16px 10px #333); }       
    25% { -webkit-filter:drop-shadow(0px 16px 15px #333); }       
    50% { -webkit-filter:drop-shadow(0px 16px 20px #333); }       
    75% { -webkit-filter:drop-shadow(0px 16px 25px #333); }       
    100% { -webkit-filter:drop-shadow(0px 16px 30px #333); }       
}

/*************************/
/* SVG Filter Animations */
/*************************/

.fA_shadow {
  filter: url(#f1);
}

/*************************/
/* JavaScript Animations */
/*************************/
.jA_shadow {
    filter: url(#f1);
    stroke: none !important;
}

.jA_shadow_expanding {
    filter: url(#f1);
    stroke: none !important;
    fill: #CCC !important;
}

.jA_shadow_large {
    filter: url(#f2);
     stroke: none !important;
    fill: #CCC !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<h1>CSS Keyframes</h1>
<p>The downside here is that the animation seems to require attaching to the svg element, which causes all of the circles to animate their drop shadows</p>
<svg id="svg_keyframes" width="1000" height="280">
    <g transform="translate(120, 140)">
        <circle r="110"/>
        <text dx="-1.5em">Circle 1</text>
    </g>
    <g transform="translate(420, 140)">
        <circle r="110"/>
        <text dx="-1.5em">Circle 2</text>
    </g>
</svg>


<h1>CSS Keyframes referencing SVG Filters</h1>
<p>Unfortunately it seems that this approach simply doesn't work. The idea was that the class would change triggering a keyframe which would progressively change the filter being applied by specifying gradually expanding filters</p>
<svg id="svg_filterKeyFrames" width="1000" height="280">
    <defs>
        <filter id="f1" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="4" />
            <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="10" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
        <filter id="f2" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="7" />
            <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="15" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
         <filter id="f3" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="10" />
            <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="20" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
         <filter id="f4" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="13" />
            <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="25" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
         <filter id="f5" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="16" />
            <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="30" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
    </defs>
     <g transform="translate(120, 140)">
        <circle class="kf_Shadow1" r="110"/>
        <text dx="-4.5em">Shadow should change</text>
    </g>
    <g transform="translate(420, 140)">
        <circle class="kf_Fill1" r="110"/>
        <text dx="-4.5em">Colour should change</text>
    </g>
     <g transform="translate(720, 140)">
        <circle class="kf_DropShadow1" r="110"/>
        <text dx="-5.5em">Drop Shadow should change</text>
    </g>
</svg>

<h1>SVG Filters Animations</h1>
<p>SVG filter animations are another way to tackle this problem, but it seems that they behave very similar to a CSS filter in that because they are shared all of the elements update</p>
<svg id="svg_filterAnimation" width="1000" height="280">
    <defs>
        <filter id="f1" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="4" />
            <feGaussianBlur id="blur1" result="blurOut" in="matrixOut" stdDeviation="10" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
    </defs>
     <g transform="translate(120, 140)">
        <circle class="fA_shadow" r="110"/>
        <text dx="-1.5em">Circle 1</text>
    </g>
     <g transform="translate(420, 140)">
        <circle class="fA_shadow" r="110"/>
        <text dx="-1.5em">Circle 2</text>
    </g>
    <animate xlink:href="#blur1" attributeName="stdDeviation" from="10" to="30" dur="2s" begin="0s" repeatCount="indefinite"/>
</svg>

<h1>JavaScript Animations</h1>
<p>Animation via JavaScript is another approach, this uses D3 but the issue here is changing the size of gaussian blur that operates on the shadow is incredibly difficult as demonstrated in Circle 2</p>
<svg id="svg_javaScriptAnimation" width="1000" height="280">
    <defs>
        <filter id="f1" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="4" />
            <feGaussianBlur id="blur1" result="blurOut" in="matrixOut" stdDeviation="10" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
         <filter id="f2" x="-40%" y="-40%" height="200%" width="200%">
            <feOffset result="offOut" in="SourceAlpha" dx="0" dy="4" />
            <feGaussianBlur id="blur1" result="blurOut" in="matrixOut" stdDeviation="30" />
            <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
    </defs>
     <g transform="translate(120, 140)">
        <circle class="jA_shadow" r="110"/>
        <circle class="circle" r="110"/>
        <text dx="-1.5em">Circle 1</text>
    </g>
     <g transform="translate(420, 140)">
        <circle class="jA_shadow_expanding" r="110"/>
        <circle class="circle" r="110"/>
        <text dx="-1.5em">Circle 2</text>
    </g>
     <g transform="translate(720, 140)">
        <circle class="jA_shadow_large" r="80"/>
        <circle class="circle" r="110"/>
        <text dx="-1.5em">Circle 3</text>
    </g>
    <animate xlink:href="#blur1" attributeName="stdDeviation" from="10" to="30" dur="2s" begin="0s" repeatCount="indefinite"/>
</svg>


person Ian    schedule 30.06.2015    source источник


Ответы (1)


CSS-переход и CSS-анимация могут использоваться только в тех случаях, когда то, что вы хотите анимировать, контролируется CSS. Скажем, вы можете использовать его, если хотите оживить stroke-width. Но это очень ограничено.

Можно использовать <animate> для анимации фильтров SVG. Скажем, вы можете добавить id="blur1" к <feGaussianBlur> из f1 и использовать это для анимации: jsfiddle

<animate xlink:href="#blur1" attributeName="stdDeviation"
from="10" to="30" dur="1s" begin="0s" repeatCount="indefinite"/>

Атрибут begin теоретически может быть привязан к событию, скажем, mouseover, но ваш пробег может варьироваться, потому что он привязан к задаче фильтрации, что совершенно бесполезно.

Третий вариант — анимировать его с помощью JavaScript requestAnimationFrame. Вам нужно будет написать много кода, и он не будет с GPU-ускорением, но вы всегда получите то, что хотите.

person Alan Tam    schedule 30.06.2015
comment
Просто глядя на JSFiddle, и это именно то, что мне нужно... Анимация на самом деле должна происходить при щелчке или изменении класса, но я предполагаю, что это не упрощает задачу? - person Ian; 30.06.2015
comment
Вы можете запустить SMIL одним щелчком мыши, просто измените атрибут начала. - person Robert Longson; 30.06.2015
comment
@RobertLongson Просто смотрю на попытку изменить это - анимированный объект не кажется «привязанным» к элементу как таковому. Так что, если я увеличу масштаб и получу 100 кругов в SVG, нужно ли мне будет 100 анимированных элементов? - person Ian; 30.06.2015
comment
Я изменил jsfiddle, чтобы запускать SMIL при нажатии. jsfiddle.net/kyL24uaf/1 - person Alan Tam; 30.06.2015
comment
@AlanTam хорошо, это имеет смысл, и я могу это сделать. Есть ли способ остановить анимацию? Вроде автоматически сбрасывается на размытие 10 в конце анимации? - person Ian; 30.06.2015
comment
@Ian Если ваши 100 кругов анимируются вместе, вам нужен только один <animate>. В противном случае вам нужно 100. Справедливо? - person Alan Tam; 30.06.2015
comment
@Ian Чтобы он остался, добавьте fill="freeze" к <animate>. jsfiddle.net/kyL24uaf/2 - person Alan Tam; 30.06.2015
comment
@Ian Вы можете завершить это программно через endElement. - person Alan Tam; 30.06.2015
comment
@RobertLongson В этом случае мы не можем использовать begin="click", потому что анимация привязана к элементу feGaussianBlur, а не к кругу. - person Alan Tam; 30.06.2015
comment
@AlanTam - хорошо, думаю, я слежу. У меня есть новая скрипка с 3 кругами jsfiddle.net/IPWright83/kyL24uaf/4 если я нажму на один, я хочу, чтобы анимировался только этот. Я не могу понять, как анимация распределяется между всеми экземплярами... если я щелкну один, не должно ли это вызвать animate1, просто используя этот элемент в качестве SourceGraphic? Или это потому, что все они используют один и тот же фильтр? Также есть ли способ анимировать несколько свойств одновременно? - person Ian; 30.06.2015
comment
Все они используют один и тот же фильтр, поэтому все они обновляются. У вас может быть несколько анимированных элементов, каждый из которых анимирует свое свойство. - person Robert Longson; 30.06.2015
comment
@AlanTam Вы можете использовать begin. begin=elementid.click — это то, что вам нужно, где elementid — это идентификатор элемента, на который вы нажимаете. - person Robert Longson; 30.06.2015
comment
@RobertLongson Я собрал пример того, что у меня есть до сих пор - пока ничего не работает так, как мне бы хотелось jsfiddle.net/IPWright83/pjwqp75y . В конечном итоге мне нужно изменить размер круга + увеличить размер тени/отклонения размытия, способного работать индивидуально с элементами, когда их может быть несколько сотен. - person Ian; 30.06.2015
comment
@Ian Вам понадобится несколько сотен фильтров. - person Robert Longson; 30.06.2015
comment
@RobertLongson: Да, чего я действительно не хочу делать. Я даже готов сделать это на JS, но я изо всех сил пытаюсь заставить это работать с моим D3 примером внизу - person Ian; 30.06.2015
comment
@RobertLongson Мне удалось найти способ, используя всего 20 фильтров для необходимого количества кругов - используя JavaScript. Окно проверки 4 внизу jsfiddle.net/IPWright83/pjwqp75y использует D3 и анимирует атрибут фильтра на период анимации к одному из 20 различных фильтров - person Ian; 30.06.2015