Skip to content

Commit

Permalink
Add exceptions for scale and opacity properties on SVG attribute #524
Browse files Browse the repository at this point in the history
  • Loading branch information
juliangarnier committed Aug 20, 2022
1 parent ca71829 commit aa87abb
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 47 deletions.
30 changes: 22 additions & 8 deletions lib/anime.es5.js
Original file line number Diff line number Diff line change
Expand Up @@ -689,13 +689,13 @@ var anime = (function () {

function getAnimatables(targets) {
var parsed = parseTargets(targets);
return parsed.map(function (t, i) {
return parsed.map(function (el, i) {
return {
target: t,
target: el,
id: i,
total: parsed.length,
transforms: {
list: getElementTransforms(t),
list: getElementTransforms(el),
last: null,
string: emptyString
}
Expand Down Expand Up @@ -826,7 +826,7 @@ var anime = (function () {
} // SVG Responsive utils


function getParentSvg(pathEl, svgData) {
function getPathParentSvg(pathEl, svgData) {
var svg = svgData || {};
var parentSvgEl = svg.el || getParentSvgEl(pathEl);
var rect = parentSvgEl.getBoundingClientRect();
Expand Down Expand Up @@ -856,7 +856,7 @@ var anime = (function () {
isTargetInsideSVG: false,
property: property,
el: pathEl,
svg: getParentSvg(pathEl),
svg: getPathParentSvg(pathEl),
totalLength: +getTotalLength(pathEl) * (p / 100)
};
};
Expand All @@ -871,7 +871,7 @@ var anime = (function () {
function getPathProgress(pathObject, progress) {
var pathEl = pathObject.el;
var isPathTargetInsideSVG = pathObject.isTargetInsideSVG;
var parentSvg = getParentSvg(pathEl, pathObject.svg);
var parentSvg = getPathParentSvg(pathEl, pathObject.svg);
var p = getPathPoint(pathEl, progress, 0);
var p0 = getPathPoint(pathEl, progress, -1);
var p1 = getPathPoint(pathEl, progress, +1);
Expand All @@ -890,6 +890,21 @@ var anime = (function () {
}
}

function isValidSvgAttribute(el, propertyName) {
var elIsSvg = is.svg(el);
if (!elIsSvg) return;
if (propertyName === 'opacity') return; // Return false and to use CSS opacity animation instead (already better default values (opacity: 1 instead of 0))

if (propertyName in el.style || propertyName in el) {
if (propertyName === 'scale') {
var elParentNode = el.parentNode;
return elParentNode && elParentNode.tagName === 'filter'; // Only consider scale as a valid SVG attribute on filter element
}

return true;
}
}

function rgbToRgba(rgbValue) {
var rgba = rgbExecRgx.exec(rgbValue) || rgbaExecRgx.exec(rgbValue);
var r = +rgba[1];
Expand Down Expand Up @@ -957,8 +972,7 @@ var anime = (function () {
if (is.obj(el)) {
return animationTypes.OBJECT;
} else if (is.dom(el)) {
if (!is.nil(el.getAttribute(prop)) || is.svg(el) && (prop in el.style || prop in el)) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
// if (!is.nil(el.getAttribute(prop))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (!is.nil(el.getAttribute(prop)) || isValidSvgAttribute(el, prop)) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes

if (arrayContains(validTransforms, prop)) return animationTypes.TRANSFORM; // Handle CSS Transform properties differently than CSS to allow individual animations

Expand Down
2 changes: 1 addition & 1 deletion lib/anime.es5.min.js

Large diffs are not rendered by default.

28 changes: 20 additions & 8 deletions lib/anime.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,13 +541,13 @@ function parseTargets(targets) {

function getAnimatables(targets) {
const parsed = parseTargets(targets);
return parsed.map((t, i) => {
return parsed.map((el, i) => {
return {
target: t,
target: el,
id: i,
total: parsed.length,
transforms: {
list: getElementTransforms(t),
list: getElementTransforms(el),
last: null,
string: emptyString
}
Expand Down Expand Up @@ -663,7 +663,7 @@ function getParentSvgEl(el) {

// SVG Responsive utils

function getParentSvg(pathEl, svgData) {
function getPathParentSvg(pathEl, svgData) {
const svg = svgData || {};
const parentSvgEl = svg.el || getParentSvgEl(pathEl);
const rect = parentSvgEl.getBoundingClientRect();
Expand Down Expand Up @@ -694,7 +694,7 @@ function getPath(path, percent) {
isTargetInsideSVG: false,
property,
el: pathEl,
svg: getParentSvg(pathEl),
svg: getPathParentSvg(pathEl),
totalLength: +(getTotalLength(pathEl)) * (p / 100)
}
}
Expand All @@ -708,7 +708,7 @@ function getPathPoint(pathEl, progress, offset = 0) {
function getPathProgress(pathObject, progress) {
const pathEl = pathObject.el;
const isPathTargetInsideSVG = pathObject.isTargetInsideSVG;
const parentSvg = getParentSvg(pathEl, pathObject.svg);
const parentSvg = getPathParentSvg(pathEl, pathObject.svg);
const p = getPathPoint(pathEl, progress, 0);
const p0 = getPathPoint(pathEl, progress, -1);
const p1 = getPathPoint(pathEl, progress, +1);
Expand All @@ -721,6 +721,19 @@ function getPathProgress(pathObject, progress) {
}
}

function isValidSvgAttribute(el, propertyName) {
const elIsSvg = is.svg(el);
if (!elIsSvg) return;
if (propertyName === 'opacity') return; // Return false and to use CSS opacity animation instead (already better default values (opacity: 1 instead of 0))
if (propertyName in el.style || propertyName in el) {
if (propertyName === 'scale') {
const elParentNode = el.parentNode;
return elParentNode && elParentNode.tagName === 'filter'; // Only consider scale as a valid SVG attribute on filter element
}
return true;
}
}

// RGB / RGBA Color value string -> RGBA values array

function rgbToRgba(rgbValue) {
Expand Down Expand Up @@ -789,8 +802,7 @@ function getAnimationType(el, prop) {
if (is.obj(el)) {
return animationTypes.OBJECT;
} else if (is.dom(el)) {
if (!is.nil(el.getAttribute(prop)) || (is.svg(el) && (prop in el.style || prop in el))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
// if (!is.nil(el.getAttribute(prop))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (!is.nil(el.getAttribute(prop)) || isValidSvgAttribute(el, prop)) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (arrayContains(validTransforms, prop)) return animationTypes.TRANSFORM; // Handle CSS Transform properties differently than CSS to allow individual animations
if (prop in el.style) return animationTypes.CSS; // All other CSS properties
if (!is.und(el[prop])) return animationTypes.OBJECT; // Handle DOM elements properies that can't be accessed using getAttribute()
Expand Down
28 changes: 20 additions & 8 deletions lib/anime.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,13 @@

function getAnimatables(targets) {
const parsed = parseTargets(targets);
return parsed.map((t, i) => {
return parsed.map((el, i) => {
return {
target: t,
target: el,
id: i,
total: parsed.length,
transforms: {
list: getElementTransforms(t),
list: getElementTransforms(el),
last: null,
string: emptyString
}
Expand Down Expand Up @@ -669,7 +669,7 @@

// SVG Responsive utils

function getParentSvg(pathEl, svgData) {
function getPathParentSvg(pathEl, svgData) {
const svg = svgData || {};
const parentSvgEl = svg.el || getParentSvgEl(pathEl);
const rect = parentSvgEl.getBoundingClientRect();
Expand Down Expand Up @@ -700,7 +700,7 @@
isTargetInsideSVG: false,
property,
el: pathEl,
svg: getParentSvg(pathEl),
svg: getPathParentSvg(pathEl),
totalLength: +(getTotalLength(pathEl)) * (p / 100)
}
}
Expand All @@ -714,7 +714,7 @@
function getPathProgress(pathObject, progress) {
const pathEl = pathObject.el;
const isPathTargetInsideSVG = pathObject.isTargetInsideSVG;
const parentSvg = getParentSvg(pathEl, pathObject.svg);
const parentSvg = getPathParentSvg(pathEl, pathObject.svg);
const p = getPathPoint(pathEl, progress, 0);
const p0 = getPathPoint(pathEl, progress, -1);
const p1 = getPathPoint(pathEl, progress, +1);
Expand All @@ -727,6 +727,19 @@
}
}

function isValidSvgAttribute(el, propertyName) {
const elIsSvg = is.svg(el);
if (!elIsSvg) return;
if (propertyName === 'opacity') return; // Return false and to use CSS opacity animation instead (already better default values (opacity: 1 instead of 0))
if (propertyName in el.style || propertyName in el) {
if (propertyName === 'scale') {
const elParentNode = el.parentNode;
return elParentNode && elParentNode.tagName === 'filter'; // Only consider scale as a valid SVG attribute on filter element
}
return true;
}
}

// RGB / RGBA Color value string -> RGBA values array

function rgbToRgba(rgbValue) {
Expand Down Expand Up @@ -795,8 +808,7 @@
if (is.obj(el)) {
return animationTypes.OBJECT;
} else if (is.dom(el)) {
if (!is.nil(el.getAttribute(prop)) || (is.svg(el) && (prop in el.style || prop in el))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
// if (!is.nil(el.getAttribute(prop))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (!is.nil(el.getAttribute(prop)) || isValidSvgAttribute(el, prop)) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (arrayContains(validTransforms, prop)) return animationTypes.TRANSFORM; // Handle CSS Transform properties differently than CSS to allow individual animations
if (prop in el.style) return animationTypes.CSS; // All other CSS properties
if (!is.und(el[prop])) return animationTypes.OBJECT; // Handle DOM elements properies that can't be accessed using getAttribute()
Expand Down
6 changes: 3 additions & 3 deletions src/animatables.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ function parseTargets(targets) {

export function getAnimatables(targets) {
const parsed = parseTargets(targets);
return parsed.map((t, i) => {
return parsed.map((el, i) => {
return {
target: t,
target: el,
id: i,
total: parsed.length,
transforms: {
list: getElementTransforms(t),
list: getElementTransforms(el),
last: null,
string: emptyString
}
Expand Down
19 changes: 16 additions & 3 deletions src/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function getParentSvgEl(el) {

// SVG Responsive utils

function getParentSvg(pathEl, svgData) {
function getPathParentSvg(pathEl, svgData) {
const svg = svgData || {};
const parentSvgEl = svg.el || getParentSvgEl(pathEl);
const rect = parentSvgEl.getBoundingClientRect();
Expand Down Expand Up @@ -108,7 +108,7 @@ function getPath(path, percent) {
isTargetInsideSVG: false,
property,
el: pathEl,
svg: getParentSvg(pathEl),
svg: getPathParentSvg(pathEl),
totalLength: +(getTotalLength(pathEl)) * (p / 100)
}
}
Expand All @@ -122,7 +122,7 @@ function getPathPoint(pathEl, progress, offset = 0) {
function getPathProgress(pathObject, progress) {
const pathEl = pathObject.el;
const isPathTargetInsideSVG = pathObject.isTargetInsideSVG;
const parentSvg = getParentSvg(pathEl, pathObject.svg);
const parentSvg = getPathParentSvg(pathEl, pathObject.svg);
const p = getPathPoint(pathEl, progress, 0);
const p0 = getPathPoint(pathEl, progress, -1);
const p1 = getPathPoint(pathEl, progress, +1);
Expand All @@ -135,6 +135,19 @@ function getPathProgress(pathObject, progress) {
}
}

export function isValidSvgAttribute(el, propertyName) {
const elIsSvg = is.svg(el);
if (!elIsSvg) return;
if (propertyName === 'opacity') return; // Return false and to use CSS opacity animation instead (already better default values (opacity: 1 instead of 0))
if (propertyName in el.style || propertyName in el) {
if (propertyName === 'scale') {
const elParentNode = el.parentNode;
return elParentNode && elParentNode.tagName === 'filter'; // Only consider scale as a valid SVG attribute on filter element
}
return true;
}
}

export {
setDashoffset,
getPath,
Expand Down
6 changes: 3 additions & 3 deletions src/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import {
} from './animatables.js';

import {
getPathProgress
getPathProgress,
isValidSvgAttribute,
} from './svg.js';

import {
Expand All @@ -52,8 +53,7 @@ export function getAnimationType(el, prop) {
if (is.obj(el)) {
return animationTypes.OBJECT;
} else if (is.dom(el)) {
if (!is.nil(el.getAttribute(prop)) || (is.svg(el) && (prop in el.style || prop in el))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
// if (!is.nil(el.getAttribute(prop))) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (!is.nil(el.getAttribute(prop)) || isValidSvgAttribute(el, prop)) return animationTypes.ATTRIBUTE; // Handle DOM and SVG attributes
if (arrayContains(validTransforms, prop)) return animationTypes.TRANSFORM; // Handle CSS Transform properties differently than CSS to allow individual animations
if (prop in el.style) return animationTypes.CSS; // All other CSS properties
if (!is.und(el[prop])) return animationTypes.OBJECT; // Handle DOM elements properies that can't be accessed using getAttribute()
Expand Down
45 changes: 32 additions & 13 deletions tests/visual/svg.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<svg width="128" height="128" viewBox="0 0 128 128">
<filter id="displacementFilter">
<feTurbulence type="turbulence" numOctaves="2" baseFrequency="0" result="turbulence"/>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"/>
<feDisplacementMap in2="turbulence" in="SourceGraphic" xChannelSelector="R" yChannelSelector="G"/>
</filter>
<polygon style="filter: url(#displacementFilter)" stroke="#FED28B" fill="transparent"/>
</svg>
Expand All @@ -82,11 +82,11 @@
const path2El = document.querySelector('#path-without-d-attribute-2');

const dashOffsetAnimation = anime({
targets: ['line', 'circle', 'polygon', 'polyline', 'rect'],
targets: ['line', 'circle', 'polygon', 'polyline', 'rect', 'path'],
strokeDashoffset: [anime.setDashoffset, 0],
easing: 'easeInOutSine',
translateX: 100,
// opacity: 0, NEEDS FIX ?
translateX: [-100, 0],
opacity: .5,
duration: 500,
complete: () => {
test('stroke-dasharray on line 1 element', () => {
Expand All @@ -109,6 +109,9 @@
});
}
});
test('Opacity animation should default to 1', () => {
return expect(line1El.style.opacity).toBe('1');
});
test('stroke-dashoffset on line 1 element', () => {
return expect(line1.getAttribute('stroke-dashoffset')).toBe('138.59292602539062');
});
Expand Down Expand Up @@ -188,22 +191,38 @@
});

// Filters tests
const feTurbulenceEl = document.querySelector('#filters-tests feTurbulence');
const filterPolygonEl = document.querySelector('#filters-tests polygon');
const feTurbulenceEl = document.querySelector('#filters-tests feTurbulence');
const feDisplacementMapEl = document.querySelector('#filters-tests feDisplacementMap');

console.log(typeof feTurbulenceEl, feTurbulenceEl instanceof SVGElement);
console.log(typeof filterPolygonEl, filterPolygonEl instanceof SVGElement);

const filterAnimation = anime({
targets: [filterPolygonEl, feTurbulenceEl, '#filters-tests feDisplacementMap'],
points: '64 128 8.574 96 8.574 32 64 0 119.426 32 119.426 96',
baseFrequency: .05,
scale: 15,
const polyginPointsAnimation = anime({
targets: [feTurbulenceEl, feDisplacementMapEl],
baseFrequency: [.05, 1],
scale: [15, 1],
loop: true,
direction: 'alternate',
easing: 'easeInOutExpo'
});

test('Scale property should be set as an attribute on SVG filter elements', () => {
return expect(feDisplacementMapEl.getAttribute('scale')).toBe('15');
});

const filterAnimation = anime({
targets: filterPolygonEl,
points: [
'64 68.64 8.574 100 63.446 67.68 64 4 64.554 67.68 119.426 100',
'64 128 8.574 96 8.574 32 64 0 119.426 32 119.426 96'
],
translateX: [-100, 0],
scale: [.5, 1],
easing: 'easeInOutExpo'
});

test('Scale property should be set as a CSS transform on non SVG filter elements', () => {
return expect(filterPolygonEl.style.transform).toBe('translateX(-100px) scale(0.5)');
});

test('Non stylistic SVG attribute should be declared in came case', () => {
return expect(feTurbulenceEl.hasAttribute('baseFrequency')).toBe(true);
});
Expand Down

0 comments on commit aa87abb

Please sign in to comment.