Table Of Contents
feDisplacementMap
Planted:
Status: seed
<feDisplacementMap>
is a SVG filter primitive.
It distorts an input (the source) to take the shape of a 2nd input (the map).
If your unfamiliar with SVG filters, you can find a note explaining the fundamentals here.
The primitive takes 2 inputs:
in
(source) &in2
(map).
The source asset can passed straight to the in
attribute.
The map asset 1st needs to be captured using the <feImage>
.
The result can then be passed to the in2
attribute.
Basic Example
localhost:3000
import "./styles.css";
// see Loading Asset Problem section for why the following is being done.
const feImage = document.querySelector('feImage');
const url = feImage.getAttribute('href');
fetch(url)
.then((response) => response.blob())
.then((blob) => {
const objURL = URL.createObjectURL(blob);
feImage.setAttribute('href', objURL);
});
import "./styles.css"; // see Loading Asset Problem section for why the following is being done. const feImage = document.querySelector('feImage'); const url = feImage.getAttribute('href'); fetch(url) .then((response) => response.blob()) .then((blob) => { const objURL = URL.createObjectURL(blob); feImage.setAttribute('href', objURL); });
Loading Asset Problem
The <feImage>
primitive is used to load the asset used as the map.
When using this primitive, normally we would use the asset's URL as the value of the href
attribute. For example:
<feImage href="https://garden.bradwoods.io/images/towel.png" ... />
.
Due to security concerns, if:
- this asset is coming from another domain &
- the
<feImage>
is being used as the map for<feDisplacementMap>
, we have to use a different approach.
One solution is to use a blob (used in the example above):
- fetch the asset data on the client,
- turn it into a blob &
- use this blob as the
href
value of<feImage>
.
index.js
const myFeImage = document.querySelector('feImage');
const url = feImage.getAttribute('href');
fetch(url)
.then((response) => response.blob())
.then((blob) => {
const objURL = URL.createObjectURL(blob);
myFeImage.setAttribute('href', objURL);
});
Another solution is to use a data URL (used in examples below). This can be used when you have defined an SVG image in the DOM that you want to use as the map:
- capture the
<svg>
element, - encode it,
- capture your
<feImage>
element & - use the encoded URI as the
href
value of<feImage>
.
index.js
const uRIComponent = document.getElementById('myMapSvgAsset').outerHTML;
const encodedURI = encodeURIComponent(uRIComponent);
const myFeImage = document.querySelector('feImage');
// A URL encoded fragment has to be an SVG Element with a namespace attribute.
myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);
How it Works
<feDisplacementMap>
has the following attributes:
scale
: defines the strength & direction of the distortion.xChannelSelector
: defines which of the map assets 4 color channels (red, green, blue, alpha) should be applied to distort the x-axis.yChannelSelector
: defines which of the map assets 4 color channels (red, green, blue, alpha) should be applied to distort the y-axis.
Irrelevant of which color channel is selected for an axis, the following applies:
- A value of 127 will result in no change.
- A value above 127 will move the pixel towards the scale value.
- A value below 127 will move the pixel away from the scale value.
Imagine we set xChannelSelector
to red & the yChannelSelector
to blue.
Then apply a map asset that covered the source with 1 color, rgba(127, 127, 127, 1)
.
This would result in no pixels moving.
localhost:3000
import "./styles.css";
const uRIComponent = document.getElementById('myMap').outerHTML;
const encodedURI = encodeURIComponent(uRIComponent);
const myFeImage = document.querySelector('feImage');
// A URL encoded fragment has to be an SVG Element with a namespace attribute (set on the element in index.html).
myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);
import "./styles.css"; const uRIComponent = document.getElementById('myMap').outerHTML; const encodedURI = encodeURIComponent(uRIComponent); const myFeImage = document.querySelector('feImage'); // A URL encoded fragment has to be an SVG Element with a namespace attribute (set on the element in index.html). myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);
If you decrease the red channel's value to rgba(51, 127, 127, 1)
, the output will translate to the right.
This is because:
- the xChannelSelector is set to red,
- the x-position of the image is 0 &
- the scale is set to 100.
If you increase the blue channel's value to rgba(127, 127, 204, 1)
, the output will translate up.
This is because:
- the
yChannelSelector
is set to blue, - the y-position of the image is 0 &
- the scale is set to 100.
The Identity Map
The Identity Map is a special map that will scale an image equally in all directions. The color values gradually decline from a maximum at 1 edge to a minimum at the opposite edge.
Create an Identity Map
- Create a layer & fill it with black,
- create another layer and fill it with a left to right gradient from
rgba(255, 0, 0, 1)
torgba(255, 0, 0, 0)
, - create another layer and fill with a top to bottom gradient from
rgba(0, 0, 255, 1)
torgba(0, 0, 255, 0)
& - set the blending mode of this last layer to screen.
localhost:3000
import "./styles.css";
import "./styles.css";
Use an Identity Map
localhost:3000
import "./styles.css";
const uRIComponent = document.getElementById('myMap').outerHTML;
const encodedURI = encodeURIComponent(uRIComponent);
const myFeImage = document.querySelector('feImage');
// A URL encoded fragment has to be an SVG Element with a namespace attribute (set on the element in index.html).
myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);
import "./styles.css"; const uRIComponent = document.getElementById('myMap').outerHTML; const encodedURI = encodeURIComponent(uRIComponent); const myFeImage = document.querySelector('feImage'); // A URL encoded fragment has to be an SVG Element with a namespace attribute (set on the element in index.html). myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);
If you increase or decrease (negative numbers are valid) the scale
value, the output will zoom in & out.
Examples
Sophisticated
localhost:3000
import "./styles.css";
const feImages = document.querySelectorAll('feImage');
const url = feImages[0].getAttribute('href');
fetch(url)
.then((response) => response.blob())
.then((blob) => {
const objURL = URL.createObjectURL(blob);
feImages.forEach((feImage) => {
feImage.setAttribute('href', objURL);
});
});
import "./styles.css"; const feImages = document.querySelectorAll('feImage'); const url = feImages[0].getAttribute('href'); fetch(url) .then((response) => response.blob()) .then((blob) => { const objURL = URL.createObjectURL(blob); feImages.forEach((feImage) => { feImage.setAttribute('href', objURL); }); });
Glass Lense
localhost:3000
import "./styles.css";
const uRIComponent = document.getElementById('myMap').outerHTML;
const encodedURI = encodeURIComponent(uRIComponent);
const myFeImage = document.querySelector('feImage');
// A URL encoded fragment has to be an SVG Element with a namespace attribute (set on the element in index.html).
myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);
import "./styles.css"; const uRIComponent = document.getElementById('myMap').outerHTML; const encodedURI = encodeURIComponent(uRIComponent); const myFeImage = document.querySelector('feImage'); // A URL encoded fragment has to be an SVG Element with a namespace attribute (set on the element in index.html). myFeImage.setAttribute(`href`, `data:image/svg+xml;charset=utf-8,${encodedURI}`);