Free Open-Source Icons

Original Source: https://smashingmagazine.com/2021/08/open-source-icons/

It’s incredible to see people from all across the world putting all their love and lots of hours into something that they then release for free, for everybody to use. They help projects on a tight budget shine, without asking for anything in return — and, of course, they never cease to inspire.

In this post, we’ll celebrate some of these wonderful freebies that we came across recently. All of these free icons, illustrations, avatars, and animations have slightly different licenses (so please check them), but they are free to use in private and commercial work. But of course, the credit is always much appreciated.

If you’re interested in more goodies like these ones, please do take a look at our lovely email newsletter, so you can get tips like these delivered right into your inbox.

Open-Source Flag Icons

Icons are a fantastic way to help convey meaning and add some character to a design. An icon set that comes in particularly handy in e-commerce projects is Flagpack. It includes more than 250 open-source flag icons — from Andorra to Zimbabwe.

The Flagpack icons are optimized for small sizes which makes them a perfect fit for all your UI needs. They come with several predefined styles or you can customize the look to your liking, if you prefer. Two versions available: Designers can use the Flagpack in Figma and Sketch, developers can install it directly within their code project (React, Vue, and Svelte are supported). Nice!

Boring Avatars And Absurd Illustrations

Do you need some SVG-based avatars for your project? Boring Avatars has got you covered. But don’t let the name fool you: The avatars are anything but boring.

The tiny React library generates custom, SVG-based, round avatars from any username and color palette. Six different styles are available, abstract patterns just like friendly smileys. You can either choose a random color palette or create your own from scratch to tailor the avatars to your design. A friendly little detail.

Another fantastic resource with a name that might leave you puzzled for a moment are the Absurd Illustrations that Diana Valeanu created. Her series of illustrations combines absurdity and a deep sense of childishness and naivety to take every user on an individual journey through their imagination. A beautiful celebration of the imperfect that we’re sometimes missing in this digital era, absurdity that, well, makes sense.

Open-Source Health Icons

When people from all across the globe share the same vision and work together on one common goal, great things can happen. Things like the Health Icons project. The project is a volunteer effort to create a ‘global good’ for health projects all over the world. It includes more than 870 public-domain icons that can be used for free in any type of project.

Blood types, body parts and organs, diagnostics, medications, medical devices, vehicles, and people — the set covers everything health-related you could possibly ask for. Each icon comes in three different styles (outlined, filled, negative) and is available in SVG and PNG formats. There’s also a Figma plugin that makes it easy to find and include icons in your design.

Open-Source Life Science Icons

Vaccines, viruses, genetics, physiology, bioinformatics. These are just some of the topics that the fascinating Bioicons set that Simon Duerr created covers. In total, you’ll find 1,700 open-source life science illustrations in the set — easily searchable and filterable, of course.

The site supports two modes: you can copy the icons as SVG into the clipboard and paste them directly into Illustrator to change colors or delete parts. Alternatively, you can download the SVG to use it on the web, in graphics software, and even Microsoft Office products. An encyclopedia-like set — if you don’t find what you’re looking for there, you probably won’t find it anywhere.

Weather-Themed Icons And CSS

222 weather-themed icons, complete with 28 moon phases, the Beaufort wind force scale, and maritime wind warnings — that’s what you’ll get in the free Weather Icons set that Erik Flowers created.

You can copy and paste the icons into your favorite design app right from the site. CSS makes it easy to style and customize the icons just like you would do with text, so feel free to change their color, add shadows, or scale, rotate, or flip them to make the icons fit into your design nicely. Popular weather API classes are also supported, by the way, in case you should need it.

Summer And Tourism Icons

Barbecues, a trip to the beach, exploring a new city — summer is the time to make memories. To bring some of the summer feeling into your projects, you might want to take a look at the Summer Time and Tourism icon sets which the folks at Design Zone offer for free download. Each set includes 20 vector icons (in colored and linear versions) and comes in AI, EPS, and PNG formats. You can use the icons for both personal and commercial projects. Happy summer!

Flat Illustrations With Character

No more boring business websites! The Flat Illustrations set that Getillustrations.com offers is bound to add some personality to your project. It features free compositions for businesses, online stores, and marketing. Characters in various poses and scenes, colorful, relaxed, and friendly.

The site features ever-changing free sets (link attribution is required), and also the commercial versions come with customizable vectors for Figma, Sketch, Ai, SVG, AdobeXD, and Iconjar. Good vibes guaranteed!

A World Of Open-Source Icons

What started as a semester project by Interaction and Communication Design students, has grown to a comprehensive open-source emoji and icon library: OpenMoji. Almost 4,000 icons have been carefully designed and reviewed for the project, ranging from UX and technology-themed emojis to animals, nature, food, people, activities, places, and much more. The icons are available in colored as well as outlined versions in SVG and PNG formats.

Hand-Curated Mesh Gradients

Beautiful gradients? Yes, please! If you don’t have the time to create them yourself or are looking for some fresh inspiration, Gene Maryushenko’s hand-curated collection of mesh gradients is sure to have you covered.

Unhappy with what he found online, Gene created 100 gradients that will make your site stand out, without being loud. The gradients can be downloaded for free as JPEGs. CSS and SVG will be added later. Enjoy!

Open Peeps

584,688 possible combinations. That’s the number of different characters you could create with Pablo Stanley’s hand-drawn illustration library Open Peeps.

Open Peeps lets you mix and match different vector elements to create diverse personalities: combine clothing and hairstyles, change emotion with facial expressions, set the scene with different poses — the possibilities are sheer endless. And if you’re in a hurry, Pablo also prepared some ready-to-download Peeps that you can use right away. A great way to add a handmade touch to your design.

Free Vector Illustrations

A cow kidnapped by aliens, a dropped ice cream cone with a sad face, the Lochness monster emerging from the water. These are some of the fun error state animations that the folks at Pixel True Studios offer for free download in their set of vector illustrations and animations.

Apart from error state animations, the set includes more than 500 illustrations, icons, and animations to depict everything a web project could call for: backgrounds, emoji, avatars, and more. The illustrations are available in SVG, PNG, AI, Sketch, and Figma, the animations are made with Lottie. A great way to add a fun and friendly touch to a design.

Thank You!

A huge thank-you to the creative minds who designed these freebies — and of course to everyone who gives back to the community in one sense or another. The web wouldn’t be the same without you. ❤️

DPI Daylighting — Total Branding

Original Source: http://feedproxy.google.com/~r/abduzeedo/~3/FlCfaHl7ZOk/dpi-daylighting-total-branding

DPI Daylighting — Total Branding
DPI Daylighting — Total Branding

abduzeedo08.17.21

Alphabet Design Agency shared an incredible total branding and visual identity for DPI Daylighting, a leader in commercial daylighting systems. By harnessing daylight through dynamic, environmentally friendly solutions, they provide visually and thermally comfortable architectural experiences. We were tasked to create a modern and sophisticated identity that showcased the product and attracted both architects, designers and project managers.

architect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsImage may contain: screenshot and cartoonImage may contain: screenshotarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsImage may contain: moonImage may contain: outdoor

 

“Alphabet have been very proactive in their approach and meeting deadlines without any compromises on creativity and output. Despite our products and industry being out of their usual line of work, their quick understanding and interpretation was commendable. We are extremely happy we chose Alphabet and would do so again.”

Rajeev Moudgil, CEO, DPI Daylighting

architect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsImage may contain: typographyarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutions

architect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutions

In a similar style to the FLUX typeface, we also created a fully bespoke supporting suite of iconography which helped create a visual language for the key product features as well as wayfinding and supporting graphics throughout the offices and factory.

architect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutionsarchitect architecture branding  daylight DPI Daylighting grid light lighting polycarbonate solutions

To see the full case study, visit  Alphabet Design Agency Ltd  website; madebyalphabet.com or check them out on:

Behance
Instagram


Create an Abstract Image Slideshow with OGL, GLSL, and GSAP

Original Source: http://feedproxy.google.com/~r/tympanus/~3/bokeaZOutQY/

In today’s tutorial, we’ll dig (very) deep into the WebGL and GLSL worlds to create a really cool image carousel whose entire animation will depend on a single value; every calculation will be made inside the shader.

How this tutorial is structured

I’ve organized the tutorial in multiple steps, some of them really short and others “less-short”, all because I want to guide you through the entire process and explain you the reasoning behind them.

At the beginning of each step you can find a link with the code diff on the GitHub repository, so that you can see exactly what has changed and where, just in case you end up with different results.

Let’s get started!

Step 01 – Initial setup

View code diff

First, let’s create the files we need using OGL’s triangle screen shader example as the base.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Triangles' grid carousel [OGL+GSAP]</title>
<link rel="stylesheet" href="./app.scss">
</head>
<body>
<div id="canvas-wrapper" data-canvas-wrapper></div>

<script src="./app.js"></script>
</body>
</html>

app.scss

html,
body {
margin: 0;
overflow: hidden;
}

body {
background-color: #112233;
}

#canvas-wrapper {
height: 100vh;
position: relative;
width: 100%;

canvas {
height: 100%;
position: absolute;
width: 100%;
}
}

app.js

import { Renderer, Program, Mesh, Triangle } from 'ogl'
import { gsap } from 'gsap'

class WebGLCarousel {
constructor() {
this.wrapper = document.querySelector('[data-canvas-wrapper]')
}

init() {
this._createRenderer()
this._createScene()
this._addListeners()
this._onResize()

gsap.ticker.add(() => {
this.program.uniforms.uTime.value += 0.01
this.renderer.render({ scene: this.mesh })
})
}

_createRenderer() {
this.renderer = new Renderer()
this.gl = this.renderer.gl

this.wrapper.appendChild(this.gl.canvas)

this.gl.clearColor(1, 1, 1, 1)
}

_createScene() {
this.geometry = new Triangle(this.gl)

this.program = new Program(this.gl, {
vertex: require('./shaders/effect.vertex.glsl'),
fragment: require('./shaders/effect.fragment.glsl'),
uniforms: {
uTime: { value: 0 },
uProgress: { value: 0 }
}
})

this.mesh = new Mesh(this.gl, {
geometry: this.geometry,
program: this.program
})
}

_addListeners() {
window.addEventListener('resize', this._onResize.bind(this), { passive: true })
}

_onResize() {
this.renderer.setSize(this.wrapper.clientWidth, this.wrapper.clientHeight)
}
}

const app = new WebGLCarousel()
app.init()

effect.fragment.glsl

precision highp float;

uniform float uTime;

varying vec2 vUv;

void main() {
float sTime = sin(uTime);
float cTime = cos(uTime);

vec3 color = vec3(vUv.x*0.8 + sTime*0.2, vUv.y, 0.5 + cTime*0.5);

gl_FragColor = vec4(color, 1.0);
}

effect.vertex.glsl

attribute vec2 uv;
attribute vec2 position;

varying vec2 vUv;

void main() {
gl_Position = vec4(position, 0.0, 1.0);

vUv = uv;
}

The app.js file doesn’t do that much at the moment; it creates a basic scene with a mesh that covers the entire screen and renders a color gradient.

You might have noticed that I used gsap.ticker.add(() => { … }) instead of the classic requestAnimationFrame(() => { … }). This is because there are screens and devices that have a really high refresh rate; this would make our application run faster (in terms of speed of animation) than what we want.

GSAP’s ticker makes sure that our application won’t exceed 60fps even on screens that run at, for instance, 144fps.

Now let’s install the packages we need by running the following commands from the terminal:

$ yarn add gsap ogl
$ yarn add -D parcel-bundler

On the first run, Parcel will install a bunch of other development dependencies like sass and glslify because it will see that we’re using SCSS and GLSL code. The good thing about Parcel is that everything will simply work without any additional configuration.

Open the package.json file and add the following commands:

{

"scripts": {
"dev": "rm -rf dist && parcel src/index.html",
"build": "rm -rf dist && parcel build src/index.html –no-source-maps"
},


}

Now, go to the terminal and run

$ yarn dev

then open a new browser tab and navigate to http://localhost:1234; if you see a fullscreen color gradient that changes over time like the image below, then you’re good to proceed!

Step 02 – Add debug panel

View code diff

Now let’s add a debug panel using Tweakpane. This will come in handy many times during development because it allows updating values in real-time without going back and forth from the code editor.

Install it from the terminal

yarn add tweakpane

and then add it to the app.js file


import { Pane } from 'tweakpane'


const pane = new Pane()

pane.addInput(this.program.uniforms.uProgress, 'value', { label: 'uProgress', min: 0, max: 1, step: 0.01 })

We just added a configuration panel for the uProgress uniform but the effect is not visible on the application, so let’s edit the effect.fragment.glsl file in order to do so.


uniform float uProgress;

void main() {
vec3 color = vec3(vUv, uProgress);

gl_FragColor = vec4(color, 1.0);
}

What we just did here is simple: the color variable’s red and green channels depends on the vUv coordinates, and the blue channel is determined by the value of the uProgress uniform.

Now, if you try to edit the uniform from the panel you can see that the colors of the gradient change.

Step 03 – Draw a triangle

View code diff

In order to draw shapes, we must deal with the conversion of cartesian coordinates to polar coordinate, arctangents, and other “not that easy” stuff. All that stuff is outside the scope of this tutorial so I’ll just paste here the code that I took from The Book of Shaders.

Shape.js

#define PI 3.14159265359
#define TWO_PI 6.28318530718

float Shape(in vec2 st, in vec2 p, in float size, in float sides, in float blur) {
vec2 pos = vec2(p) – st;
float a = atan(pos.x, pos.y) + PI;
float r = TWO_PI / sides;
float d = cos(floor(.5 + a/r)*r – a) * length(pos);

float color = smoothstep(size + blur, size – blur, d);

return color;
}

#pragma glslify: export(Shape)

Please notice the last line that says #pragma glslify: export(Shape). That is the glslify syntax that will allow us to “import” this file into other GLSL files. glslify is basically the “NodeJS of shaders”.

Let’s import this new file into the effect.fragment.glsl file

#pragma glslify: Shape = require(./modules/Shape)

From now on, we can use the Shape function; let’s start by adding a single triangle at the center of the screen.

// vUv go from 0 to 1 on both axes, we have to
// remap them so that they go from -1 to +1.
// This is a common practice to easily align
// things at the center of the screen.
vec2 uv = vUv*2.0 – 1.0;

// Generate a triangle …
vec2 pos = vec2(0.0);
float size = 0.1;
float sides = 3.0;
float blur = 0.001;
float triangle = Shape(uv, pos, size, sides, blur);

// … and add it to the color variable
color += triangle;

Now, if you’ve done everything correctly, you can see a black screen with a white triangle at its center.

Step 04 – make everything resolution-independent

View code diff

Ok, everything we’ve done so far works ALMOST like a charm. If you resize the browser window you see that the triangle gets squashed.

This can be avoided with just one line of code in the fragment shader, but first, we must provide it the size of the canvas.

First, in the app.js file, import the OGL’s Vec2 object and add a new uResolution uniform to the shader declaration

// Add Vec2 to he modules imported from OGL
import { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl'

// Add the new uResolution uniform
this.program = new Program(this.gl, {
uniforms: {

uResolution: {
value: new Vec2(
this.gl.canvas.clientWidth,
this.gl.canvas.clientHeight
)
}
}
})


// Update the uResolution uniform on Resize
_onResize() {

this.program.uniforms.uResolution.value = new Vec2(
this.gl.canvas.clientWidth,
this.gl.canvas.clientHeight
)
}

Now let’s work on the fragment shader

// Tell the shader to expect a new uResolution uniform of type vec2
uniform vec2 uResolution;

// Right after declaring the uv variable, update it this way
uv.x *= uResolution.x / uResolution.y;

Go back to the browser and try resizing the browser window. If you’ve done everything right, the triangle shouldn’t deform anymore.

Step 05 – Generate a grid

View code diff

Yeah, a single triangle is cool, but we need to generate lots of them.

The first thing that might come to mind is a couple of for loops that iterate through both the X and Y axes and draw a triangle at a certain set of coordinates. While this is correct, there’s a more efficient way to do that (your GPU will thank you).

Add this line to the fragment shader

vec2 gv = fract(uv*5.0);

This calculation takes the uv variable subdivides it by the value you define and makes it repeat on both axes.

When adopting this technique, it’s common practice to name the new set of UVs gv, which stands for grid uv.

Now, if you replace uv with gv in the triangle formula …

float triangle = Shape(gv, pos, size, sides, blur);

you see that a lot has changed

But hey, what’s going on?

The reason is really simple; the origin of the gv coordinates is at the bottom-left corner. So, we simply have to update the pos variable in order to place the triangle at the center again.

vec2 pos = vec2(0.5);

And here’s our grid of triangles, all without any additional work for the GPU.

Then why not add some axes to use as reference and help us align things?
Add these lines to the fragment shader:

// Get the size of a single pixel
float pixel = 1.0 / uResolution.x;

// Draw two red lines on both axes by using
// the values of uv.x and uv.y
color.r += 1.0 – step(pixel*4.0, abs(uv.x));
color.r += 1.0 – step(pixel*4.0, abs(uv.y));

Easy peasy!

Step 06 – Refactor

View code diff

Before things start to get complicated, why not refactor what we have now in order to better organize our code?

Edit the fragment shader this way

precision highp float;

uniform float uProgress;
uniform vec2 uResolution;

varying vec2 vUv;

#pragma glslify: Shape = require(./modules/Shape)

// We moved the triangle creation to a separate function
// because we will use it multiple times later
float Triangle(vec2 uv, vec2 position) {
float size = 0.2;
float sides = 3.0;
float blur = 0.001;

return Shape(uv, position, size, sides, blur);
}

// The creation of the grid will need its own function,
// because it will contain a lot of code later.
float Tiles(vec2 uv) {
float result = 0.0;
vec2 gv = fract(uv*5.0);

result += Triangle(gv, vec2(0.5));

return result;
}

void main() {
vec2 uv = vUv*2.0 – 1.0;
uv.x *= uResolution.x / uResolution.y;

vec3 color = vec3(0.0);

// Add the output of the Tiles function to
// the output color.
color += Tiles(uv);

// Debug axes
float pixel = 1.0 / uResolution.x;
color.r += 1.0 – step(pixel*4.0, abs(uv.x));
color.r += 1.0 – step(pixel*4.0, abs(uv.y));

gl_FragColor = vec4(color, 1.0);
}

Now if you check the browser nothing should have changed, and that’s good news.

Step 07 – Avoid triangles being “cut”

View code diff

Everything we’ve done so far works like a charm, but what happens if you increase the size of the triangles or move them to another position? Simple, they will be drawn outside the boundaries and be “cut”.

There’s a simple trick to avoid this kind of situation, and it consists in drawing on the neighbor tiles as well.

Add this code to the Tiles function:

for (float y = -1.0; y <= 1.0; y++) {
for (float x = -1.0; x <= 1.0; x++) {
// Get the coordinates of the neighbor tile
vec2 tileOffset = vec2(x, y);

// Draw the triangle
result += Triangle(gv – tileOffset, vec2(0.5));
}
}

The code above loops through all the tiles that surround our current tile (including the tile we’re on) and draws a triangle on each one of them. This technique does not actually draw on those tiles, but simply at the same coordinates.

This gives us some flexibility with positioning the triangles allowing us to give the illusion that the triangles are positioned outside their boundaries.

Be aware that since we’re looping through a 3×3 grid, what we’re drawing must not exceed that space, otherwise it will be cut. To avoid this we can increase the size of the grid (5×5 or even more), but this would increase the work for the GPU.

Just like the previous step, if you check the browser, nothing should have changed (at least visually), but we’re actually drawing 9 triangles now.

Step 08 – Shift triangles on even rows

View code diff

Now, let’s shift our triangles a bit to make things a bit more interesting. The technique is pretty easy.

Add this to the Tiles function…

// This is a common practice to get a unique identifier of each tile.
// With this, each tile's ID will be something like
// vec2(1.0, 2.0), vec2(0.0, 4.0), and so on.
// Those values are basically the indices of the current row and column.
vec2 id = floor(uv*5.0);

… and this to the nested for loops

// On even rows, we must shift the triangles by
// half of their width, so we use the mod function to
// determine if the current row is even or odd.
// If the current row is even, the mod function returns 1.0,
// so we must divide it by half.
vec2 tileShift = vec2(mod(id.y, 2.0)*0.5, 0.0);

// Subtract the value of tileShift from the coordinates of the triangle
result += Triangle(gv – tileOffset – tileShift, vec2(0.5));

Now if you go back to the browser you should see that some of the triangles are shifted now, and thanks to the technique we used in the previous step they “overflow” their boundaries.

Step 09 – Add triangles pointing up

View code diff

Now it’s time to add another set of triangles to the mix, but this time they will be pointing in the opposite direction.

So, the first thing we have to do is creating a new module that exports a Rotate function:

Rotate.glsl

mat2 Rotate(float angle) {
float s = sin(angle);
float c = cos(angle);

return mat2(c, -s, s, c);
}

#pragma glslify: export(Rotate)

Then, import it into the fragment shader just like we did with the Shape function:

#pragma glslify: Rotate = require(./modules/Rotate)

Add then this code inside the nested for loops

// Create a new set of UVs named `st` and rotate them around their center
// Notice that PI is not a variable available in GLSL out of the box,
// but it's defined inside the Shape module.
vec2 st = (gv – 0.5)*Rotate(PI) + 0.5;

// Offset the new UVs by half of the width plus
// an arbitrary value for the Y axis.
st -= vec2(0.5, 0.37);

// Add the triangle
result += Triangle(st – tileOffset + tileShift, vec2(0.5));

Also, for future reference, we will need to be able to identify each triangle, exactly like we did with the tiles using the floor function; let’s do it:

// Get a unique identifier of each triangle …
vec2 triangleID = id + tileOffset;

// … and use its y component instead of the y component
// of the tile ID. The result is the same, but we'll
// play with it later.
vec2 tileShift = vec2(mod(triangleID.y, 2.0)*0.5, 0.0);

If you go back to the browser, you should see that now there is a new set of triangles that point up.

Step 10 – Manage the size of the grid

View code diff

All good, but a grid that covers the entire screen is not what we want, so let’s determine its size.

In app.js, add a uGridSize uniform

this.program = new Program(this.gl, {

uniforms: {

uGridSize: {
value: new Vec2(5, 3)
}
}
})

and make it configurable via the debug panel

pane.addInput(this.program.uniforms.uGridSize.value, 'x', { label: 'Grid size X', min: 0, max: 20, step: 1 })

pane.addInput(this.program.uniforms.uGridSize.value, 'y', { label: 'Grid size Y', min: 0, max: 20, step: 1 })

Then add the uGridSize uniform to the fragment shader

uniform vec2 uGridSize;

Inside the nested for loops, determine whether the current triangle must be drawn or not

// If the value of triangleID.x goes past uGridSize.x, then
// isVisible is set to 0.0, which means that the triangle won't
// be displayed on the X axis.
float isVisible = step(abs(triangleID.x), uGridSize.x);

// We do the same calculation for the Y axis.
// Multiplying the previous value by this new calculation
// results in a "rectangular" mask.
isVisible *= step(abs(triangleID.y), uGridSize.y);

It’s now time to draw only the triangles that we want to be visible. To make everything more readable we should refactor a bit:

// 'd' and 'u' are the two "layers" of triangles,
// respectively pointing down and up.
// Each layer is multiplied by the value of the 'isVisible' variable,
// making all the exceeding triangles invisible.

float d = Triangle(gv – tileOffset – tileShift, vec2(0.5));
d *= isVisible;

float u = Triangle(st, vec2(0.5, 0.4));
u *= isVisible;

// Add the sum of the 'd' and 'u' layers to the
// 'result' output variable
result += d+u;

We’re now able to determine the size of our grid of triangles.

Step 11 – Align the grid to the center

View code diff

Technically, our grid is aligned to the center of the screen, but not visually. Try setting both the grid values to zero.; something is obviously not good.

To fix this, we have to manually offset the UVs.

// Move the triangles ttoward the bottom-left corner
// by adding some value. 0.25 is enough in this case.
vec2 gv = fract(uv*5.0 + 0.25);
vec2 id = floor(uv*5.0 + 0.25);

Now, the grid is perfectly aligned to the center of the screen.

Step 12 – Add textures

View code diff

Our job with the grid is done (for now); now it’s time to work with textures.

To be able to work with static files we have to install the parcel-plugin-static-files-copy plugin from the terminal

yarn add -D parcel-plugin-static-files-copy

This way, we are able to work with static files by simply putting them inside a /static directory.

After that, let’s add the code to load and manage the textures to the app.js file.

// Import the Texture module from OGL
import { Renderer, Program, Mesh, Triangle, Vec2, Texture } from 'ogl'

// Define the URLs of the textures inside the constructor() method
constructor() {
this.texturesURLs = [
'/images/image-01.jpg',
'/images/image-02.jpg',
'/images/image-03.jpg'
]
}

// Add these two methods to the class
//
// _loadTexure() loads a single texture and adds
// it to this.textures[], returning a Promise object
//
// _loadTextures() loads all the textures in
// this.texturesURLs[] and returns a Promise object
//

_loadTexture(url, index) {
return new Promise(resolve => {
const img = new Image()
img.src = url

img.onload = () => {
this.textures[index] = new Texture(this.gl, {
image: img
})

resolve()
}
})
}

_loadTextures() {
return new Promise(resolve => {
this.textures = []

const promises = this.texturesURLs.map((url, index) => this._loadTexture(url, index))

Promise
.all(promises)
.then(() => resolve())
})
}

// Refactor the init() method so that the class waits for
// the textures to load before doing anything.
init() {
this._createRenderer()

this._loadTextures()
.then(() => {
this._createScene()
this._createDebugPanel()
this._addListeners()
this._onResize()

gsap.ticker.add(() => {
this.renderer.render({ scene: this.mesh })
})
})
}

// Add a uTexture0 uniform to the program
this.program = new Program(this.gl, {

uniforms: {

uTexture0: {
value: this.textures[0]
}
}
})

Now, let’s add the texture to the fragment shader.

// Tell the shader to expect a uTexture0 uniform.
// In GLSL, textures are a sampler2D data type.
uniform sampler2D uTexture0;

// Inside the main() function, define a tex0 variable,
// and use that for the final output.
// The UVs are re-mapped to go from 0.0 to 1.0.
vec4 tex0 = texture2D(uTexture0, uv*0.5 + 0.5);

// For this, we could've also write 'gl_FragColor = tex0'
// since tex0 is a variable of type vec4.
gl_FragColor = vec4(tex0.rgb, 1.0);

Et voilà, our texture is there!

Step 13 – Add ‘cover’ effect to textures

View code diff

Depending on the texture, you might see weird things on the edges, like this

That is the last pixel of the texture that gets repeated infinitely, causing bad visual issues.
What we can do to avoid this is emulating CSS’s cover feature, so that the image will cover the entire screen no matter the size of the screen.

Luckily, someone else already wrote the code to do it. Let’s create a new Cover module with that code.

Cover.glsl

vec2 Cover(vec2 uv, vec2 screenSize, vec2 imageSize) {
vec2 s = screenSize;
vec2 i = imageSize;

float rs = s.x / s.y;
float ri = i.x / i.y;

vec2 new = rs < ri ? vec2(i.x * s.y / i.y, s.y) : vec2(s.x, i.y * s.x / i.x);
vec2 offset = (rs < ri ? vec2((new.x – s.x) / 2.0, 0.0) : vec2(0.0, (new.y – s.y) / 2.0)) / new;
vec2 st = uv * s / new + offset;

return st;
}

#pragma glslify: export(Cover)

Import that module inside the fragment shader

#pragma glslify: Cover = require(./modules/Cover)

You may have noticed that the Cover function requires additional data that we currently don’t have: the size of the image.

Edit the app.js file by adding a new uniform with the size of the image to provide to the fragment shader.

// 1. Add a 'uTexture0Size' uniform to the program
this.program = new Program(this.gl, {

uniforms: {

uTexture0Size: {
value: new Vec2()
}
}
})

// 2. Set the value of 'uTextureSize' in the render loop
gsap.ticker.add(() => {
this.program.uniforms.uTexture0Size.value = new Vec2(
this.textures[0].width,
this.textures[0].height
)
})

Add the uTexture0Size uniform to the fragment shader.

uniform vec2 uTexture0Size;

Now we have all the data we need to use the Cover function.

main() {

// Use this new set of UVs to create the texture.
//
// Notice that we're using the original vUv variable here.
// That's because the 'Cover' function expect a set of UVs that
// go from 0 to 1.
vec2 coverUV = Cover(vUv, uResolution, uTexture0Size);
vec4 tex0 = texture2D(uTexture0, coverUV);


}

Now check the browser and try resizing the screen. The image covers perfectly the area, just like with CSS.

Step 14 – Mask the texture

View code diff

Now it’s time to “merge” the texture and the grid of triangles; the process is extremely easy.

Our grid is a black layer with an N amount of white triangles; what happens if we multiply the texture’s colors by the value of the grid? Where the mask is black, the color of the texture would be hidden, and when the color is white the texture is fully visible.

Edit the fragment shader this way:

// Create the masks with the triangles
float mask0 = Tiles(uv);

// This is where the magic happens. We hide all the
// areas of the texture where the mask is black.
vec3 layer0 = tex0.rgb*mask0;

color = layer0;

gl_FragColor = vec4(color, 1.0);

And here it is. An image made of triangles whose number is configurable.

Step 15 – Fade out the image

View code diff

It’s time to work on the transition effect, so let’s start with fading out the image using the uProgress uniform.

To do so, we’re going to add a new progress parameter to the Tiles function; this parameter will be used to manage the whole animation.

Start by updating the fragment shader

float Tiles(vec2 uv, float progress) {

// 'alpha' is the opacity of the triangle.
// It goes from 1 to 0 depending on the value of
// the 'progress' parameter.
float alpha = smoothstep(1., 0., progress);

// The value of both 'd' and 'u' is multiplied by
// the value of 'alpha', making everything fade out
// while the value of 'progress' increases.
float d = Triangle(gv – tileOffset – tileShift, vec2(0.5));
d *= isVisible;
d *= alpha;

float u = Triangle(st, vec2(0.5, 0.4));
u *= isVisible;
u *= alpha;


}

..

void main() {

float progress0 = smoothstep(0.1, 0.9, uProgress);

float mask0 = Tiles(uv, progress0);


}

It works, but it could be way more interesting if we randomize the speed of each triangle’s animation. We could do this by generating a random number using the randomID variable and using it as the first parameter of the smoothstep function.

We don’t have a function to generate random values yet, so let’s add it.

Create a Random.glsl file

float Random(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p+45.32);
return fract(p.x * p.y);
}

#pragma glslify: export(Random)

Import it into the fragment shader

#pragma glslify: Random = require(./modules/Random)

Then use it to generate a random value for the smoothstep function.

// The 'clamp' function limits the minimum and maximum values
// respectively to 0.1 and 0.9
float fadeStart = clamp(Random(triangleID), 0.1, 0.9);

float alpha = smoothstep(fadeStart, 0., progress);

Much, much better; don’t you think?

Step 16 – Add the 2nd texture

View code diff

Adding the 2nd texture is mostly a matter of duplicating what we already did for the 1st one.

Edit the fragment shader with the code to display the 2nd texture

// Declare the uniforms
uniform sampler2D uTexture1;
uniform vec2 uTexture1Size;

void main() {

// The progress for the 2nd texture runs backwards,
// so the 1st parameter is higher than the 2nd.
float progress1 = smoothstep(1.0, 0.2, uProgress);

// Create the mask for the 2nd texture, just like
// what we did for the 1st one.
float mask1 = Tiles(uv, progress1);

// After creating the 1st texture, create the 2nd one.
// Don't forget to re-declare the 'coverUV' variable.
coverUV = Cover(vUv, uResolution, uTexture1Size);
vec4 tex1 = texture2D(uTexture1, coverUV);

// Apply the mask to the texture
vec3 layer1 = tex1.rgb*mask1;

// This is the key part.
// Based on the value of 'uProgress', show one layer or the other.
// If 'uProgress' is 0, 'layer0' is completely visible,
// Otherwise, if 'uProgress' is 1, layer1 is visible.
// Every value in-between will make the layers overlap.
color = mix(layer0, layer1, uProgress);
}

Now add the proper code to the app.js file

// Add the uniforms to the program
this.program = new Program(this.gl, {

uniforms: {

uTexture1: {
value: this.textures[1]
},
uTexture1Size: {
value: new Vec2()
}
}
})

// Update the value of 'uTexture1Size' in the render loop
gsap.ticker.add(() => {

this.program.uniforms.uTexture1Size.value = new Vec2(
this.textures[1].width,
this.textures[1].height
)


})

And that’s it! Now the two images swap depending on the value of the uProgress uniform.

Step 17 – Animate triangles’ scale

View code diff

Our basic effect is in place, now it’s time to make things more interesting. Let’s start by scaling the triangles during the animation.

At the moment, the size of the triangles is hardcoded, so let’s edit the Triangle function to make it expect a new size parameter:

float Triangle(vec2 uv, vec2 position, float size) {
float sides = 3.0;
float blur = 0.001;

return Shape(uv, position, size, sides, blur);
}

Then, define a size variable inside the nested for loops like this:

// Goes from 0.26 to 0.9 based on the value of 'progress'
float size = mix(0.26, 0.9, progress);

Finally, add the size variable to both the Triangle functions:

float d = Triangle(gv – tileOffset – tileShift, vec2(0.5), size);

float u = Triangle(st, vec2(0.5, 0.4), size);

It works, but all the triangles have all the same size, all the time; adding some randomness here too would make things a bit better:

// Define a 'sizeFactor' variable generated from the ID of
// the triangle, whose minimum value is 0.3.
float sizeFactor = max(Random(triangleID), 0.3);

// Multiply 'progress' by the value of 'sizeFactor'.
// This is enough for adding randomness to the
// triangles' scale.
float size = mix(0.26, 0.9, progress*sizeFactor);

Good! Now the triangles scale randomly during the animation. But there’s one more detail that we need to work on.

Now, when the new image starts fading in, it seems like the triangles “bounce back” to their original position; the cause is the progress1 variable:

// This goes from 1 to 0, meaning that the whole animation
// will run backwards
float progress1 = smoothstep(1.0, 0.2, uProgress);

Fixing it is really simple; we must edit the formula to make it go from -1 to 0. Edit that line like this:

// 'smoothstep' always goes from 0 to 1, but if we
// subtract 1 at the end, the result goes from -1 to 0
//
// Please note that we swapped the 1st and 2nd parameter
// top make the animation go forwards.
float progress1 = smoothstep(0.2, 1.0, uProgress) – 1.0;

And here we have it!

Step 18 – Offset each triangle

View code diff

Another thing we can do here is adding a very subtle offset to each triangle.

I’m sure you’ve already guessed how we’re going to do that: yes, we’re will use the triangleID variable to generate a random offset for each axis and add it to the triangle’s coordinate.

Add this inside the nested for loops:

// The 'Random' function generate a number that goes from 0 to 1,
// but for the offset values we must remap them so that they
// go from -1 to 1
float randomOffsetX = Random(triangleID.y);
randomOffsetX = (randomOffsetX – 0.5) * 2.0;

float randomOffsetY = Random(triangleID.x);
randomOffsetY = (randomOffsetY – 0.5) * 2.0;

vec2 randomOffset = vec2(randomOffsetX, randomOffsetY)*0.4;

Then, subtract randomOffset from the triangles’ coordinates:

float d = Triangle(gv – tileOffset – tileShift – randomOffset, vec2(0.5), size);

vec2 st = (gv – tileOffset – tileShift – randomOffset)*Rotate(PI) + 0.5;

Beautiful, uh?

Step 19 – Show full image

View code diff

Now, why don’t we push things even further?

First, let’s scale the triangles based on their distance from the center of the screen:

// Gets a normalized value (from 0 to 1)
float dist = distance(vec2(0.0), triangleID) / max(uGridSize.x, uGridSize.y);
dist *= 0.15;

// Add 'dist' to the size formula
float size = mix(0.02, 0.8, progress*sizeFactor)+dist;

Second, add some random rotation to each triangle:

// In order to rotate the coordinates we must multiply them by
// a mat2 variable, so let's calculate it first.
// This goes from -PI and +PI
mat2 triangleRandomRotation = Rotate(PI*(Random(triangleID) – 0.5)*2.0);

Then rotate the triangles:

// Rotate the 'gv' variable around their center.
// 'vec4(0.0, 0.4)' is there only for styling purposes; otherwise
// the triangles tend to go downwards.
vec2 rotatedGV = (gv – vec2(0.0, 0.4) – tileOffset – tileShift – randomOffset – 0.5)*triangleRandomRotation + 0.5;

// Replace 'gv' with 'rotatedGV'
float d = Triangle(rotatedGV, vec2(0.5), size);

// Apply the rotation to 'st' as well.
vec2 st = (gv – tileOffset – tileShift – randomOffset)*triangleRandomRotation + 0.5;

Lastly, we are going to show the full image along with all these triangles:

// Create a mask of the size of the grid that is used to display the full image
// The logic is the same used to determine whether to show a triangle or not.
float fullImageMask = step(abs(id.x) + 0.5, uGridSize.x);
fullImageMask *= step(abs(id.y), uGridSize.y);

// "Mask" it with the value of the triangles' grid.
// This basically creates holes in the mask.
// This step is needed because we will add this mask with the triangles, otherwise
// The final result would have areas much more luminous than the normal.
fullImageMask *= 1.0 – result;

// Set the alpha value of this mask using the `progress` parameter.
fullImageMask *= smoothstep(0.45, 0.1, abs(progress));

// Add the value of 'fullImageMask' to 'result', which already
// contains tll the triangles.
result += fullImageMask;

And here’s the final result!

Step 20 – Scale the textures and rotate the masks

View code diff

Another nice touch would be to scale the images a bit during the animation. Let’s do it:

// To scale a set of coordinates that go from 0 to 1 around
// their center, we must:
// 1. Subtract 0.5 from them
// 2. Multiply them by the value we want (values bigger than 1 will make everything smaller)
// 3. Add 0.5 back
//
// Same goes for rotations.

// Scale the UVs of the 1st texture from 1.0 to 1.05
// This makes the texture slightly smaller during the animation.
coverUV = (coverUV – 0.5)*mix(1.0, 1.05, smoothstep(0.1, 0.5, uProgress)) + 0.5;

// Scale the UVs of the 1st texture from 0.95 to 1.0
// This makes the texture slightly bigger, making it the right size at the end of the transition.
coverUV = (coverUV – 0.5)*mix(0.95, 1.0, smoothstep(0.6, 1.0, uProgress)) + 0.5;

And yes, it works!

Finally, let’s rotate the whole mask a bit. Add this at the beginning of the Tiles function:

// Create a new set of UVs to apply a rotation matrix to, and
// use them to create the grid.
//
// This adds a subtle rotation of 3/100 PI + another rotation of
// 4/100 PI depending on the value of 'progress', which goes from -1 to 1
//
// This way, the texture that fades out rotates from the 'idle' angle to
// the 'out' angle, while the texture that fades in rotates from the 'in' angle
// to the 'idle' angle.
mat2 rotateUV = Rotate(PI*0.03 + PI*mix(0.0, 0.04, progress));
vec2 uv2 = uv*rotateUV;

// Replace uv with 'uv2' on both 'gv' and 'id'
vec2 gv = fract(uv2*5.0 + 0.25);
vec2 id = floor(uv2*5.0 + 0.25);

If you’ve done everything properly, you should see it working like the video below|

Step 21 – Add the background(s)

View code diff

It’s not over yet! Now we have to add the colored background beneath the slides, whose colors are picked directly from the images and determined by a noise texture. It’s long, but not as complicated as one might think.

Let’s start by adding the noise texture to the program; I’ve chosen the Noise_18 texture from this collection from 1MAFX.

this.program = new Program(this.gl, {

uniforms: {

uNoiseTexture: {
value: this.noiseTexture
}
}
})

Then refactor the _loadTexture() method …

// We replaced the 'index' parameter with a 'params' parameter,
// which is an additional set of configuration for the Texture.
// This is needed when loading the noise texture.
_loadTexture(url, params = {}) {
return new Promise(resolve => {
const img = new Image()
img.src = url

img.onload = () => {
const texture = new Texture(this.gl, {
…params,
image: img
})

resolve(texture)
}
})
}

… and the _loadTextures() method:

// First, we load all the textures and assign the
// response to the 'this.textures' property.
// Then, load the noise texture and assign it to 'this.noiseTexture'.
_loadTextures() {
return new Promise(resolve => {
const textures = this.texturesURLs.map(url => this._loadTexture(url))
const colorThief = new ColorThief()

Promise
// Load the images for the carousel
.all(textures)
.then(res => this.textures = res)

// Load the noise texture
.then(() => {
// Since the noise will move over time, we must set
// both the 'wrapS' and 'wrapT' property to 'this.gl.REPEAT',
// making it repeat seamlessly.
return this._loadTexture('/images/Noise_18.jpg', {
wrapS: this.gl.REPEAT,
wrapT: this.gl.REPEAT
})
})
.then(res => {
this.noiseTexture = res
resolve()
})
})
}

Add then the noise texture to the fragment shader:

uniform sampler2D uNoiseTexture;

void main() {

vec2 bgNoiseUV = uv;
vec3 bgNoise = texture2D(uNoiseTexture, bgNoiseUV).rgb;

gl_FragColor = vec4(bgNoise, 1.0);
}

A bit too much, uh? Let’s tweak it a bit by making it bigger and adding some rotation to the coordinates:

vec2 bgNoiseUV = uv;
bgNoiseUV *= 0.25; // Make it bigger
bgNoiseUV *= Rotate(PI*0.25); // Rotate by 1/4 PI
vec3 bgNoise = texture2D(uNoiseTexture, bgNoiseUV).rgb;

Much, much better! Now it’s time to animate it. First, let’s add a new uTime uniform to the Program which we’ll use to move the coordinates of the noise texture

this.program = new Program(this.gl, {

uniforms: {

uTime: { value: 0 }
}

})

and update its value in the render loop

gsap.ticker.add(() => {

this.program.uniforms.uTime.value += 0.1


})

Then, define it in the fragment shader and add it to the UV coordinates of the noise texture to move it over time

uniform float uTime;

void main() {

bgNoiseUV += vec2(uTime*0.01, -uTime*0.03);


}

Cool! Now our noise is moving!

Let’s proceed by adding some color. The colors will come directly from the images, and for this, we’re going to use the color-thief library.

First, add it to the project from the terminal:

$ yarn add colorthief

And import it into app.js and create a new instance of it:

import ColorThief from 'colorthief'

const colorThief = new ColorThief()

Then, right after downloading the textures, loop through them and run ColorThief to get the dominant and secondary colors:

// Creates a 'this.color' array which contains a series
// of objects with this structure:
// {
// primary: [255, 120, 200],
// secondary: [90, 110, 210]
// }
//
// With 'getPalette()' we chose the color with index 3,
// but you can pick the color you want.
this.colors = res.map(e => {
return ({
primary: colorThief.getColor(e.image),
secondary: colorThief.getPalette(e.image)[3]
})
})

Add two new uBackground0 and uBackground1 uniforms to the Program, which contain both the primary and secondary colors of the textures.

this.program = new Program(this.gl, {

uniforms: {

uBackground0: {
value: [
this.colors[0].primary,
this.colors[0].secondary
]
},
uBackground1: {
value: [
this.colors[1].primary,
this.colors[1].secondary
]
}
}
})

Add them to the fragment shader

// This syntax tells the shader that those two uniforms
// are arrays of lenght 2 and contain vec3 variables.
uniform vec3 uBackground0[2];
uniform vec3 uBackground1[2];

Let’s now create both the backgrounds inside the main() function:

// 1. The colors go from 0 to 255, but since we need them go have a value
// between 0 and 1 we must divide them by 255
//
// 2. Setting the 'noise texture's value as the 3rd parameter,
// we can easily interpolate between the two colors.
vec3 background0 = mix(uBackground0[0] / 255., uBackground0[1] / 255., bgNoise);
vec3 background1 = mix(uBackground1[0] / 255., uBackground1[1] / 255., bgNoise);

Let’s test this:

gl_FragColor = vec4(background0, 1.0);

And it works!

The last thing we have to do is “merging” the slides with their respective backgrounds, and we’re going to use the glsl-blend package to do it. glsl-blend is a module that contains a set of blending modes for GLSL, the same blending modes that Photoshop and other graphics tools use.

Let’s start by installing it from the terminal:

$ yarn add glsl-blend

Since we simply have to put the slides on top of their backgrounds, we will need the blendNormal module:

#pragma glslify: blendNormal = require(glsl-blend/normal)

Then define two new variables that will contain the blended layers:

// Refactor the 'layer0' and 'layer1' variables to be
// of type vec4. We will need their alpha channel shortly.
vec4 layer0 = tex0*mask0;
vec4 layer1 = tex1*mask1;

// For the alpha value (3rd parameter) we use the alpha channel
// of the layers; otherwise the 'layer' lavels would overlap
// entirely the backgrounds
vec3 slide0 = blendNormal(background0, layer0.rgb, layer0.a);
vec3 slide1 = blendNormal(background1, layer1.rgb, layer1.a);

// Replace 'layer0' and 'layer1' with 'slide0' and 'slide1'
color = mix(slide0, slide1, smoothstep(0.5, 0.85, uProgress));

And here’s the final result!

Step 22 – Add carousel behavior

View code diff

Let’s start by adding the basic markup and some rough styling:

index.html

<nav id="controls">
<button data-carousel-control data-dir="-1">Prev</button>
<button data-carousel-control data-dir="1">Next</button>
</nav>

app.scss

#controls {
align-items: center;
bottom: 40px;
display: flex;
justify-content: center;
left: 0;
position: fixed;
width: 100%;

button {
margin: 0 20px;
}
}

There are a few steps to take into consideration when it comes to WebGL carousels:

Determine what will be the next texture (uTexture1, in our case)Go from the first texture to the second (animate uProgress)When the animation finishes, set the animation progress back to the initial value (uProgress = 0)Set the value of the initial texture to be the same of the second (uTexture0 = uTexture1)

After this, both uTexture0 and uTexture1 will be the same, but we actually don’t care because only uTexture0 is visible.

The better manage all this stuff we should keep all this information inside a state object

constructor() {

this.state = {
isAnimating: false,
currentTextureIndex: 0,
texture0: null,
texture1: null
}


}

Define the above object’s texture0 and texture1 properties in the _loadTextures() method after downloading them

this.state.texture0 = res[0]
this.state.texture1 = res[1]

And then replace all the references to this.textures[0] and this.textures[1] with the textures inside the this.state object. I won’t list all of them, just refer to the code diff.

Create now a _onButtonClick() method with the following code.

_onButtonClick(e) {
// Do nothing if an animation is already running
if (this.state.isAnimating) return

// Get the direction of the clicked button (defaults to 1)
const direction = Number(e.currentTarget.dataset.dir ?? 1)

// Define the index of the texture that will be set as texture1
let nextTextureIndex = this.state.currentTextureIndex + direction

if (nextTextureIndex < 0)
nextTextureIndex = this.textures.length – 1

if (nextTextureIndex >= this.textures.length)
nextTextureIndex = 0

const tl = new gsap.timeline({
onStart: () => {
// Prevent any other animation from starting
this.state.isAnimating = true

// Set the next texture to display
this.state.texture1 = this.textures[nextTextureIndex]
this.program.uniforms.uTexture1.value = this.state.texture1

// Set the background colors of the next slide
this.program.uniforms.uBackground1.value = [
this.colors[nextTextureIndex].primary,
this.colors[nextTextureIndex].secondary
]
},
onComplete: () => {
// Re-enable animations
this.state.isAnimating = false

// Reset the `uProgress` uniform …
this.program.uniforms.uProgress.value = 0

// … and set what was only the next texture as current texture
this.state.texture0 = this.textures[nextTextureIndex]
this.program.uniforms.uTexture0.value = this.state.texture0

// Same thing with the background colors
this.program.uniforms.uBackground0.value = [
this.colors[nextTextureIndex].primary,
this.colors[nextTextureIndex].secondary
]

// End of the animation. Set the new texture's index as the current one.
this.state.currentTextureIndex = nextTextureIndex
}
})

tl
.to(this.program.uniforms.uProgress, {
value: 1,
duration: 1.3
})
}

Add a new this.ui object to the class in the contructor(). It will contain more stuff later.

this.ui = {
buttons: document.querySelectorAll('[data-carousel-control]')
}

Finally, register the event listeners:

_addListeners() {

for (const button of this.ui.buttons) {
button.addEventListener('click', this._onButtonClick.bind(this), { passive: true })
}


}

Aaaaaaaand it works!

But let’s tweak it by making it rotate in one direction or the other depending on what slide we want to show.

Add a new uAnimationDirection in the program, as usual

this.program = new Program(this.gl, {

uniforms: {
uAnimationDirection: { value: 1 }
}
})

And update its value from the _onButtonClick() method

// This value can be -1 OR +1
this.program.uniforms.uAnimationDirection.value = direction

Lastly, update the fragment shader:

// Declare it
uniform float uAnimationDirection;

// Here, multiply 'progress' by the value of 'uAnimationDirection'.
// This way it rotates CCW when clicking on the 'prev' button.
mat2 rotateUV = Rotate(PI*0.03 + PI*mix(0.0, 0.04, progress*uAnimationDirection));

Pretty easy! It now rotates in both directions.

Step 23 – Add slides’ markup

View code diff

The very last thing that’s left is adding all the slides’ markup to the page, so here’s the code that you can copypaste!

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Triangles' grid carousel [OGL+GSAP]</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fjalla+One&family=Noto+Sans&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./app.scss">
</head>
<body>
<div id="canvas-wrapper" data-canvas-wrapper></div>

<div id="slides">
<div class="slide" data-slide>
<span class="slide__title | font-display" data-slide-title style="–progress: 0">We are homo ludens</span>
<span class="slide__copy | font-copy" data-slide-copy>We are the ones who play</span>
</div>

<div class="slide" data-slide>
<span class="slide__title | font-display" data-slide-title style="–progress: -110">It's dangerous</span>
<span class="slide__copy | font-copy" data-slide-copy style="opacity: 0">to go alone, take this!</span>
</div>

<div class="slide" data-slide>
<span class="slide__title | font-display" data-slide-title style="–progress: -110">Snake? Snake?</span>
<span class="slide__copy | font-copy" data-slide-copy style="opacity: 0">SNAAAAAAAAAAAKE!</span>
</div>
</div>

<nav id="controls">
<button class="font-display" data-carousel-control data-dir="-1">Prev</button>
<button class="font-display" data-carousel-control data-dir="1">Next</button>
</nav>

<script src="./app.js"></script>
</body>
</html>

app.scss

@import "functions";
@import "mixins";

* {
box-sizing: border-box;
}

html,
body {
margin: 0;
overflow: hidden;
}

body {
background-color: #112233;
}

.font-display {
font-family: 'Fjalla One', sans-serif;
}

.font-copy {
font-family: 'Noto Sans', sans-serif;
}

#canvas-wrapper {
height: 100vh;
position: relative;
width: 100%;

canvas {
height: 100%;
position: absolute;
width: 100%;
}
}

#controls {
align-items: center;
display: flex;
justify-content: space-between;
left: 0;
padding: 0 30px;
pointer-events: none;
position: fixed;
top: 50%;
transform: translateY(-50%);
touch-action: none;
width: 100%;
z-index: 5;

button {
background-color: transparent;
border: none;
color: white;
cursor: pointer;
font-size: 21px;
margin: 0 20px;
padding: 0;
pointer-events: auto;
position: relative;
text-transform: uppercase;
touch-action: auto;

&::before,
&::after {
background-color: currentColor;
content: "";
display: block;
height: 2px;
opacity: 0;
position: absolute;
transition: 0.25s transform, 0.25s opacity;
width: 100%;
}

&::before {
top: -5px;
transform: translateY(-5px);
}

&::after {
bottom: -5px;
transform: translateY(5px);
}

&:hover {

&::before,
&::after {
opacity: 1;
transform: translateY(0);
}
}
}
}

#slides {
display: grid;
grid-template-areas:
"."
"slide"
".";
grid-template-columns: 1fr;
grid-template-rows: 3fr 1fr 1fr;
height: 100%;
left: 0;
pointer-events: none;
position: fixed;
top: 0;
touch-action: none;
width: 100%;
z-index: 4;
}

.slide {
align-items: center;
align-self: center;
color: white;
display: flex;
flex-direction: column;
grid-area: slide;
pointer-events: auto;
text-align: center;
touch-action: auto;

&__title {
@include fluid-type(320px, 1400px, 40px, 100px);

clip-path: polygon(
calc(var(–progress) * 1%) 0%,
calc(var(–progress) * 1% + 110%) 0%,
calc(var(–progress) * 1% + 100%) 100%,
calc(var(–progress) * 1% – 10%) 100%);
text-shadow: 0.01em 0.04em 0 rgba(black, 0.7);
}

&__copy {
@include fluid-type(320px, 1400px, 16px, 21px);
text-shadow: 0.1em 0.1em rgba(black, 0.5);
}
}

_functions.scss

@function strip-unit($value) {
@return $value / ($value * 0 + 1);
}

_mixins.scss

@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
$u1: unit($min-vw);
$u2: unit($max-vw);
$u3: unit($min-font-size);
$u4: unit($max-font-size);

@if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
& {
font-size: $min-font-size;
@media screen and (min-width: $min-vw) {
font-size: calc(#{$min-font-size} + #{strip-unit($max-font-size – $min-font-size)} * ((100vw – #{$min-vw}) / #{strip-unit($max-vw – $min-vw)}));
}
@media screen and (min-width: $max-vw) {
font-size: $max-font-size;
}
}
}
}

Nothing too complicated here. The slides’ container is a grid element and each slide is positioned on the same cell.

Please notice that the .slide__title elements have a clip-path applied that masks them, whose position is determined by the value of a local –progress custom property. Instead of animating the whole mask, we will simply animate that single custom property using GSAP.

Now, add the slides to the this.ui object:

this.ui = {
buttons: document.querySelectorAll('[data-carousel-control]'),
slides: document.querySelectorAll('[data-slide]')
}

Then, in the _onButtonClick() method, get a reference of the current and next texture’s elements to animate

const currentSlide = this.ui.slides[this.state.currentTextureIndex]
const currentSlideTitle = currentSlide.querySelector('[data-slide-title]')
const currentSlideCopy = currentSlide.querySelector('[data-slide-copy]')

const nextSlide = this.ui.slides[nextTextureIndex]
const nextSlideTitle = nextSlide.querySelector('[data-slide-title]')
const nextSlideCopy = nextSlide.querySelector('[data-slide-copy]')

Finally, update the timeline. This moves the current mask from the idle position to the right, and che next texture’s mask from left to the idle position.

tl
.add('start')
.to(currentSlideTitle, {
'–progress': 110,
duration: 0.5
})
.fromTo(nextSlideTitle, { '–progress': -110 }, {
'–progress': 0,
duration: 1
}, '<0.1')

.to(currentSlideCopy, { opacity: 0, duration: 0.35 }, 'start+=0.1')
.to(nextSlideCopy, { opacity: 1, duration: 0.5 }, '>')

.to(this.program.uniforms.uProgress, {
value: 1,
duration: 1.5
}, 'start')

And that’s it!

Step 24 – Final tweaks

View code diff

We’re almost done, but there are still a few things left to tweak.

Let’s start by adding a slight rotation to the triangles. Edit the triangleRandomRotation variable in the fragment shader:

// Usual technique to rotate something around its center.
// The original value goes from -PI to +PI, then is
// multiplied by 0.1 (you can tweak this value)
// and multiplied again by the value of the 'progress',
// making it rotate during the animation.
float triangleAnimationRotation = PI*((Random(triangleID.yx) – 0.5) * 2.0)*0.1*progress*uAnimationDirection;

mat2 triangleRandomRotation = Rotate(triangleAnimationRotation+PI*(Random(triangleID) – 0.5)*2.0);

Also, I don’t like how the image overlaps the triangles in-between the animation, it looks really “flat”. So let’s make the triangles overlap the image. The formula is the following:

// This interpolates the value of 'result' from 1 to 0.5 based on
// the 'progress' parameter, making the mask half-transparent and
// giving the look that the triangles are overlapping the image.
fullImageMask *= 1.0 – (result * mix(1.0, 0.5, abs(progress*2.0)));

Conclusion

You did it! You finally reached the end! I know it’s been long but for this I wanted to proceed with small steps explaining the reasoning between every single step, something that I think is more useful than the result itself.

The post Create an Abstract Image Slideshow with OGL, GLSL, and GSAP appeared first on Codrops.

Gamepads turned into Monsters Illustrations

Original Source: http://feedproxy.google.com/~r/abduzeedo/~3/c11L5aTQHe0/gamepads-turned-into-monsters-illustrations-

Gamepads turned into Monsters Illustrations
Gamepads turned into Monsters Illustrations

AoiroStudio08.16.21

I always enjoyed featuring projects that are well-thought-out and completely out of the ordinary. Judging by the title, you can tell right away it’s cool, thankful to share the work of Ilya Lyakh who is an illustrator and graphic designer based in Moscow, Russian Federation. We are featuring his work titled: ‘Monster Play 19’ which is a series of popular console gamepads turned into monsters. An inspiration he took after seeing a custom gamepad, great reference to a cool result. Check it out!

For more on Ilya Lyakh via Behance

ILLUSTRATION  digitalart poster print monster gamepad xbox playstation Nintendo Lyakh

ILLUSTRATION  digitalart poster print monster gamepad xbox playstation Nintendo Lyakh

ILLUSTRATION  digitalart poster print monster gamepad xbox playstation Nintendo Lyakh

ILLUSTRATION  digitalart poster print monster gamepad xbox playstation Nintendo Lyakh

ILLUSTRATION  digitalart poster print monster gamepad xbox playstation Nintendo Lyakh

 

Links

Behance
Instagram


9 Best Free DNS Servers to Boost Privacy and Speed

Original Source: https://www.hongkiat.com/blog/free-dns-servers/

DNS is the little technology that comes into play every time one uses the web, but hardly anyone knows about it. It is the very reason you can type “google.com” to make a web search…

Visit hongkiat.com for full content.

Build Your Site The Easy Way With TagDiv Newspaper

Original Source: https://www.webdesignerdepot.com/2021/08/build-your-site-the-easy-way-with-tagdiv-newspaper/

Ready to take your business online but not sure where to start? It’s a surprisingly simple process, made all the easier by the tagDiv Newspaper theme that can shoulder the burden of code, leaving you to be creative.

Professional web designers normally begin with broad brushstrokes, roughly hewing a site, before finessing on the finer details; that’s exactly what tagDiv’s Newspaper theme lets you do, with innovative tools and a huge selection of pre-built options to choose from.

What is the Newspaper Theme?

Newspaper is the best-selling magazine theme from tagDiv. In constant development over the last twelve years, it’s been used to create world-beating sites for clients as diverse and demanding as United Nations, ShareAmerica, The Rackspace Blog, Bitcoin News, Uber Engineering, and Tech Explorist.

As the name implies, the Newspaper theme is primarily a magazine or blog theme, but you can use it for any kind of site, in any type of industry.

Packed with intuitive features that make personal web development not just possible but enjoyable, it will have your site ready to launch in no time.

It is fully compatible with Gutenberg, WordPress’ drag-n-drop site builder; it also comes with its own WYSIWYG, tagDiv Composer. All the sites built in Newspaper are fully flexible, responsive, mobile-friendly, and retina-ready. Newspaper is even fully integrated with WooCommerce so that you can transform your magazine site into a fully-featured ecommerce store.

Newspaper has been carefully coded to make the most of the upcoming Core Web Vitals update from Google, so your new site will be well-placed to rank highly on search engines.

Now that you know a little about the background of the Newspaper theme let’s look at how we can use it to build a WordPress site.

Pre-Built Websites

Sometimes it’s not just your coding skills but your design skills that are lacking; you know what kind of site that you want, you’re just hazy on the details.

This is where tagDiv’s Newspaper theme comes into its own. With a single click, you can install an entire pre-built website. And the process is so fast and convenient that you can try out as many as you like and pick the one that matches your vision.

You can even import a whole ton of pre-written content for testing the sites out, so you can see how it will look with your content without spending hours typing or copying and pasting text out of a Word doc.

There are 120 different designs covering all kinds of different industries. This means no matter what kind of site you’re building, one click, and you’re ready to go live.

Cloud Library

One step up from a full pre-built website is tagDiv’s Cloud Library. If you’ve installed one of the pre-built websites, this is probably where you want to go next.

The Cloud Library is a collection of 1600 different design elements that can be mixed and matched with live-editing to fast-track your site creation. You’ll find content blocks like headers, footers, testimonials, banners, and featured items. You can mix-n-match them however you please to create the site of your dreams.

Putting the “Cloud” into the Cloud Library are tagDiv’s servers, storing the code for each of these elements, so your installation only needs to load the code you actually use. This provides a huge boost in performance.

Composer

Lastly, the Newspaper theme provides tagDiv Composer, a specialist WordPress builder — like Gutenberg — that allows you to customize your website with absolutely no coding skills whatsoever. It’s a powerful yet simple-to-use tool that allows you to edit the elements of the Cloud Library and adapt them to your own purposes.

With Composer, you can create your own headers, footers, banners, text blocks, and more without any of the limitations of cookie-cutter design.

tagDiv Composer gives you the ultimate control over your site. You’ll find all the essential elements you need, from text and buttons to images and videos. All you have to do is drag and drop them into place.

Building a Site With tagDiv Newspaper Theme

As you can see that the fastest way to an engaging and original website is, to begin with, a pre-built site, then add the different blocks of code you want from the Cloud Library, and finally customize them with the Composer.

To make this possible, tagDiv has brought together experts from multiple disciplines, including brand strategy, project management, and web development.

It’s a slick system for quickly publishing high-quality sites that are unique to you and your business. Trusted by more than 130,000 customers worldwide, the Newspaper has earned tagDiv the status of Power Elite Author on ThemeForest and Top Author on Envato.

The Newspaper theme costs just $59, which includes six months of support from tagDiv, but for just $17.63, you can extend support to 12 months for added peace of mind.

tagDiv Newspaper is the simplest way for you to take your business online using WordPress.

 

[— This is a sponsored post on behalf of tagDiv —]

Source

p img {display:inline-block; margin-right:10px;}
.alignleft {float:left;}
p.showcase {clear:both;}
body#browserfriendly p, body#podcast p, div#emailbody p{margin:0;}

The post Build Your Site The Easy Way With TagDiv Newspaper first appeared on Webdesigner Depot.

Collective #674

Original Source: http://feedproxy.google.com/~r/tympanus/~3/05BB_7jNy9s/

Codrops Collective 674 item image

Inspirational Website of the Week: DILLINGER

A really cool design that uses some interesting typography effects. Our pick this week.

Get inspired

Codrops Collective 674 item image

Our Sponsor
Be is Now The Fastest Way to Build a Website

“I have to say that the new live builder is an absolute masterpiece. So far I used elementor, but switching to your new builder will definitely affect the speed of my work and greater customer satisfaction. Love it!” – DinoMaron

Discover more

Codrops Collective 674 item image

The State Of Mobile First and Desktop First

Is mobile first or desktop first still relevant today? An article that explores both with pros and cons for each. By Ahmad Shadeed.

Read it

Codrops Collective 674 item image

HTTP/3 From A To Z: Core Concepts (Part 1)

After almost five years in development, the new HTTP/3 protocol is nearing its final form. Learn all about it in this series by Robin Marx.

Read it

Codrops Collective 674 item image

CSS Transforms tutorial

In this blog post, Josh W Comeau takes a deep look at the “transform” property and shows some of the nifty things it can do.

Read it

Codrops Collective 674 item image

Foundations

An important message from Jeremy Keith: “[The web] is for everyone. Not just for everyone to consume, but for everyone to make.”

Read it

Codrops Collective 674 item image

Visualizing a codebase

How can we “fingerprint” a codebase to see its structure at a glance? Amelia Wattenberger explores ways to automatically visualize a GitHub repo.

Read it

Codrops Collective 674 item image

Random links with Eleventy

Learn how to generate randomized links at built time with Eleventy. By Thomas Vantuycom.

Read it

Codrops Collective 674 item image

Wicked Backgrounds

Create beautiful SVG background waves for your UI designs.

Check it out

Codrops Collective 674 item image

JSKIDPIX

A JavaScript implementation of “Kid Pix”, an old Mac drawing application for children.

Check it out

Codrops Collective 674 item image

Accessible contrast ratios and A-levels explained

All about those A’s — your guide to understanding and making use of the ratios that determine your accessible contrast.

Read it

Codrops Collective 674 item image

Grids

A super-cool grid presentation by the folks of Obys Agency.

Check it out

Codrops Collective 674 item image

Solar Storm

An audio reactive scene built with React Three Fiber by Varun Vachhar.

Check it out

Codrops Collective 674 item image

Openby.design

Custom crafted free UI design resources, for personal and commercial projects.

Check it out

Codrops Collective 674 item image

Practice / 3.js

A beautiful demo by Toshiya Marukubo.

Check it out

Codrops Collective 674 item image

Exploring the CSS Paint API: Image Fragmentation Effect

Temani Afif shows how to create an interesting fragmentation effect using the CSS Paint API.

Read it

Codrops Collective 674 item image

daisyUI

A Tailwind CSS components plugin with cool themes.

Check it out

Codrops Collective 674 item image

Paper Trader Game

A simple game where you try to make as much money as you can in just 90 seconds.

Check it out

Codrops Collective 674 item image

GitHub shortcut

New magic shortcut on GitHub: Press . on any GitHub repo and edit multiple files.

Check it out

Codrops Collective 674 item image

From Our Blog
Inspirational Websites Roundup #28

A hand-picked collection of the most beautiful and creative web designs from the past month.

Check it out

Codrops Collective 674 item image

From Our Blog
Superhero Animation Effect with SVG Filters

Learn how to create and animate a superhero-like illustration effect for the web using SVG filters and masks.

Check it out

The post Collective #674 appeared first on Codrops.

MORPHE ARCHITECTS — Branding and Visual Identity

Original Source: http://feedproxy.google.com/~r/abduzeedo/~3/sRE3iOA41nM/morphe-architects-branding-and-visual-identity

MORPHE ARCHITECTS — Branding and Visual Identity
MORPHE ARCHITECTS — Branding and Visual Identity

abduzeedo08.12.21

Newly established Athens based architecture agency, entrusted faze design studio with their identity design. Faze’s team were assigned to design the logotype, stationary, identity system and online portfolio website. “Morphe” means form in greek. Light, color and perspective is what makes form visible. Morphe is not visible in a 2D grid, but when we push that grid in the 3D space, morphe is born. 


 

Credits

Art direction by faze design studio
Photos by Yiannis Konstantinidis
Animation by Nektarios Karanikas

For more information make sure to check out faze design studio on

Behance
Instagram
Website


Best Project Management Software for Remote Teams

Original Source: https://www.hongkiat.com/blog/project-management-software/

Working with a remote team is all the rage now, not only for established companies, but also for small startups, as well as individuals who are working on their own projects. If you’re working…

Visit hongkiat.com for full content.

Superhero Animation Effect with SVG Filters

Original Source: http://feedproxy.google.com/~r/tympanus/~3/US_Fj5Z7S9w/

Superhero stories have captivated us for decades — from the fantastic worlds and characters of comic books, to the spectacular visual effects that help bring them to life in movies. In this tutorial, we’ll take inspiration from that and learn how to create and animate a superhero-like illustration effect for the web. Well, of course we don’t have the super powerful tools of big Hollywood studios available for us in our browsers, but worry not, SVG filters and masks will come to the rescue! ?

We’ll learn how to recreate this cool mutant transformation effect. In case you’re not familiar with her story, Raven Darkhölme, better known as Mystique, is a shapeshifter character from the X-Men franchise. She has a natural navy-blue skin and can manipulate her body to turn into pretty much any person she wants. That’s certainly a useful ability for running away in disguise, something that she does quite frequently. So, to add an intriguing backstory to our project, she’s portrayed in a classic-style wanted poster.

To prepare the illustrations, we’ll use Inkscape 1.1, a free open source vector drawing editor that you can download here. You can use any other vector drawing editor that you prefer. Just keep in mind that the steps will most likely be different. You also need to have at least an intermediate level of HTML, CSS and JavaScript knowledge and be familiar with GSAP, the library we’ll be using for animations.

One last thing before we start

As of now, animated SVG filters usually work better in Chrome than other browsers. Keep in mind that they can also slow down the page significantly. SVG filters are convenient and easy to use, but consider using WebGL if performance is a concern, since it’s GPU-optimized. Codrops has lots of resources about it.

How to prepare a vector illustration for the web

Inkscape is a powerful vector graphics editor full of advanced features for illustration and digital art. Plus, it’s a convenient tool to work with SVGs in a visual way, instead of writing all the code from scratch ?. We won’t really cover the illustration process in Inkscape in detail for this tutorial, but you can learn more about it from the official documentation.

Creating the drawing

Ok, let’s get started. Follow Inkscape’s quick setup guide and it will get you to an empty canvas screen. Head over to File > Document Properties in order to set the page size and units. It’s important to use pixels instead of millimeters or inches, since it’s a lot easier to work with pixels on the web.

Screen capture showing Inkscape's main screen. Document properties are open on the left with units set to pixels. The document is blank.Inkscape main screen with document properties set to pixel units.

From there, all you need to do is create your illustration using Inkscape’s tools, drawing everything using shapes and curves or importing other images to compose them on the screen. For this illustration in particular, I used Procreate on an iPad to draw the lines and imported them as PNGs into Inkscape with File > Import. Then, I used Path > Trace Bitmap to generate a high quality vector drawing from the PNG and colored by manually tracing basic shapes behind the lines. The final result was a composition of 4 different pieces: the background, foreground, mystique in her natural blue form and mystique in a transformed human form.

 All the different parts of the final composition. From left to right, first is the background with a textured effect. Second, a drawing of a disguised mystique as a blonde woman. Third, mystique in her natural blue form. Fourth is the foreground, a poster with the words "Have you seen this woman?" at the top, a blank space in the middle and "Raven Darkhölme" at the bottom.Different parts of the final composition.

Exporting an optimized SVG

Once your illustration is complete, it’s time to export the SVGs. Each one of the pieces of the composition should go in its own file, since they’ll be combined on the page. ⚠ Attention: Using Inkscape’s or your preferred editor’s native format on the web is not a good idea. These formats usually add extra data to the output that is not interpreted by browsers and just ends up making the file unnecessarily bigger.

Luckily, Inkscape comes with a super handy File > Save as… > Optimized SVG option that produces a much smaller SVG. On the export panel, it’s important to check SVG Output > Enable viewboxing, in order to make the images easier to position with CSS. The remaining defaults options are fine, but I’d encourage you to experiment a little bit with them too.

Inkscape optimized svg output options panel. All settings are set to their respective default values, except for "Enable viewboxing", which is focused and checked.Optimized SVG Output options.

If you’re really serious about optimizing your assets, using SVGO is a must. It’s available as a command line tool and web interface on the super neat website SVGOMG. Once again, I recommend you enable Prefer viewBox to width/height to make positioning and sizing more flexible. I also recommend Prettify markup in case you intend to edit the exported code manually, it makes the output more readable.

Markup and Style

Let’s start with a very simple HTML5 page structure with index.html, style.css and index.js files and an images directory to store all the exported SVGs. Starting from a very simple HTML5 page, we import our stylesheets in the head and the script at the bottom.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mystique</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<svg class="picture" viewBox="0 0 196 296">
<rect id="bg-color" x="0" y="0" width="100%" height="100%"
fill="#4a5eb2"/>
<image id="background" x="0" y="0" width="100%" height="100%"
href="images/background.svg"/>
<image id="transformed" x="0" y="0" width="100%" height="100%"
href="images/transformed.svg"/>
<image id="mystique" x="0" y="0" width="100%" height="100%"
href="images/mystique.svg"/>
<image id="foreground" x="0" y="0" width="100%" height="100%"
href="images/foreground.svg"/>
</svg>
<script src="index.js"></script>
</body>
</html>

In the body, we have a single SVG element with viewBox=“0 0 196 296”. The viewBox parameter of an SVG file defines the cropping region of the image, the first two numbers are the top left point coordinates, and the two last, the width and height, respectively. We have to use the same size we defined back in Inkscape. Next, we import the images using <image> tags, each one of them positioned on the top left (x=“0” y=“0”) and filling the entire viewport (width=“100%” height=“100%”). Behind everything, we have a rect with the color we want for the background. It’s important to give each element its own id for organization.

Styling is quite simple, a full page container with display: grid helps us to get the picture aligned to the center with place-self: center. To bring everything together and finalize the composition, we add a purple gradient to the background and a drop shadow.

html, body {
width: 100%;
height: 100%;
overflow: hidden;
}

body {
display: grid;
background: radial-gradient(at top, #5e4082, #3a124d);
padding: 1rem;
box-sizing: border-box;
}

.picture {
place-self: center;
max-width: 100%;
height: 80vh;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.5));
}

The result should look like similar to the picture below. Both images are overlapping each other, but that’s ok, we’re going to use masks to create the sliding effect.

Rendered result of the HTML page in the browser. Each part is layered on top of another, with blue mystique juxtaposed on top of blonde mystique.How the page should look like in the browser.

Setting up svg masks ?

SVG masks are elements that define the transparency of the masked objects according to the color value information inside them. If a pixel of the mask is white, the corresponding pixel of the masked object will be visible; if gray, it will be partially transparent; and in case it’s black, it will be fully transparent. Everything outside the mask region will also be fully transparent. Hopefully, the diagram below will help you understand better how masks work:

An illustration of how SVG masks work. There are 3 pictures: the first one depicts the photograph of a bird against a blurred natural background; the second shows a masks, with the bird contour in white and the background in pitch black; the third shows the mask applied to the original image, the bird is perfectly cropped from the background, which is now transparent.Masks work by defining the transparency of each pixel according to their color value. Image credits: Wikimedia Commons.

To define the mask, we add a <defs> element to the top of the SVG, with two <mask> tags inside it. One will be used to mask mystique (id="mask_mystique"), the other will be used for her transformed version (id="mask_transformed"). Each mask contains just a simple white rectangle with enough size to cover the entire visible area. Then, we apply the masks to their corresponding elements, using the mask="url(#mask_id)" attribute.

<svg class="picture" viewBox="0 0 196 296">
<defs>
<mask id="mask_mystique">
<rect class="mask" x="0" y="-100%" width="100%" height="100%" fill="white"/>
</mask>
<mask id="mask_transformed">
<rect class="mask" x="0" y="0" width="100%" height="100%" fill="white"/>
</mask>
</defs>
<rect id="bg-color" x="0" y="0" width="100%" height="100%" fill="#4a5eb2"/>
<image id="background" x="0" y="0" width="100%" height="100%" href="images/background.svg"/>
<image id="transformed" x="0" y="0" width="100%" height="100%" mask="url(#mask_transformed)" href="images/transformed.svg"/>
<image id="mystique" x="0" y="0" width="100%" height="100%" mask="url(#mask_mystique)" href="images/mystique.svg"/>
<image id="foreground" x="0" y="0" width="100%" height="100%" href="images/foreground.svg"/>
</svg>

Mystique’s mask is positioned at y="-100%", which means that it’s outside and above the viewport, hence, she will be fully transparent. Meanwhile, transformed Mystique’s mask is covering the entire viewport in white, which means that she will be fully visible. If we moved both masks 50% to the bottom, the resulting effect would be both versions of mystique being partially displayed on the screen, like the illustration below.

An image of a half transformed mystique. From the middle to the top, she's in her blue form, from the middle to the bottom, she's in her transformed, blonde form. There are labels indicating the position of the masks, mask_mystique_area at the top and mask_transformed_area at the bottom.Mystique’s mask is positioned at y="-100%", which means that it’s outside and above the viewport, hence, she will be fully transparent. Meanwhile, transformed Mystique’s mask is covering the entire viewport in white, which means that she will be fully visible. If we moved both masks 50% to the bottom, the resulting effect would be both versions of mystique being partially displayed on the screen, like the illustration below.

Adding visual effects ✨

SVG filters are super powerful tools to add some coolness to our boring simple SVGs. There’s A LOT of different filter options that are out of the scope of this tutorial, but if you want to read more about them, Codrops has plenty of tutorials explaining each one in detail.

Now, let’s move on to the code. Inside the same <defs> tag we created for masks, we add a <filter id="distort">.

<filter id="distort">
<feTurbulence
type="turbulence"
baseFrequency="0.08"
numOctaves="2"
result="turbulence"
/>
<feDisplacementMap
in2="turbulence"
in="SourceGraphic"
scale="50"
/>
</filter>

Filters are composed of primitives that can also be stacked and composed together. To create Mystique’s cool transformation effect, we’re going to use the feTurbulence primitive, which generates a Perlin Noise pattern. The attribute type of feTurbulence controls the type of turbulence that is generated, baseFrequency controls its size and numOctaves its roughness. Then, we name the output with result="turbulence".

Next we’re going to use the output of feTurbulence and combine it with the SourceGraphic (the pixels of the object to which the filter is applied) into a feDisplacementMap primitive. This primitive deforms the content of its in parameter using the color information of in2. The scale controls the length of the deformation.

? Tip: I highly recommend you to play with Yoksel’s SVG filter playground, if you want to check out how each one of these parameters affects the output of the primitives ?.

All we have to do next is apply the filters to both masks. Since the mask rectangles will be animated, to create the flowing transformation, we want to add the filters to a wrapper group <g>. Otherwise, the filter distortion would move along with the masks statically, which would not look as cool ?.

How the animation would look like if we didn’t add the groups.

To wrap up this section, here’s how the final SVG should look like:

<svg class="picture" viewBox="0 0 196 296">
<defs>
<filter id="distort">
<feTurbulence
type="turbulence"
baseFrequency="0.08"
numOctaves="2"
result="turbulence"
/>
<feDisplacementMap
in2="turbulence"
in="SourceGraphic"
scale="50"
/>
</filter>
<mask id="mask_mystique">
<g filter="url(#distort)">
<rect class="mask" x="0" y="-100%" width="100%" height="100%" fill="white"/>
</g>
</mask>
<mask id="mask_transformed">
<g filter="url(#distort)">
<rect class="mask" x="0" y="0" width="100%" height="100%" fill="white"/>
</g>
</mask>
</defs>
<rect id="bg-color" x="0" y="0" width="100%" height="100%" fill="#4a5eb2"/>
<image id="background" x="0" y="0" width="100%" height="100%" href="images/background.svg"/>
<image id="transformed" x="0" y="0" width="100%" height="100%" mask="url(#mask_transformed)" href="images/transformed.svg"/>
<image id="mystique" x="0" y="0" width="100%" height="100%" mask="url(#mask_mystique)" href="images/mystique.svg"/>
<image id="foreground" x="0" y="0" width="100%" height="100%" href="images/foreground.svg"/>
</svg>

Animating with GSAP

GSAP is an amazing JavaScript library for web animations. It can flawlessly handle animations from simple to complex with a streamlined API that gets the job done smoothly. We’re going to use it in our project to animate the masks rectangles. If you want to know more about how to use GSAP’s features, check out the official documentation here.

import gsap from "gsap"

const tl = gsap.timeline({
repeat: -1, // Makes animation repeat infinitely
yoyo: true, // Animation will go back-and-forth like a yoyo
})

tl
.to('.mask', {
translateY: '100%', // Move .mask elements down by 100%
duration: 3,
})
.to('#bg-color', {
attr: {
fill: '#ffd11b' // Change the "fill" attribute of #bg-color
},
duration: 2
}, '<+=1') // Start 1s after the previous animation

Looking into the code, first we import the GSAP and create a timeline that repeats back-and-forth. Next we animate the masks by translating them in the Y (vertical) axis and change the color of the #bg-color element to a #ffd11b yellow. The result should look like the demo from the intro:

Super cool! But what about adding some interactivity to make the animation follow the mouse? Mouse movement events can be tricky to work with on the web, because they’re fired very frequently, forcing the browser to compute animations way too many times ?. We can use lodash.throttle to help us ensure that the mouse move handler will not be triggered at a rate too fast. If you’re comfortable with it, you can also use the native requestAnimationFrame function, it’s perfect for throttling expensive animations like these.

import gsap from "gsap"

// We'll use throttle to make sure the mousemove event
// doesn't trigger too often
import { throttle } from "lodash"

const tl = gsap.timeline({
paused: true, // Start at a paused state
defaults: {
ease: 'none' // With no easing (linear)
}
})

tl
.to('.mask', {
translateY: 296,
duration: 3,
})
.to('#bg-color', {
attr: {
fill: '#ffd11b'
},
duration: 2
}, '<+=1')

const $picture = document.querySelector('.picture')

// Mouse move handler
function handleMoveEvent(ev) {
// Get the viewport rectangle of the picture
const rect = $picture.getBoundingClientRect();

// Compute the relative mouse position inside the rect
const relPos = (ev.clientY – rect.top) / (rect.bottom – rect.top)

// Use the computed value to control the animation progress
tl.progress(relPos)
}

// Bind the mousemove event, with a throttle to ensure it only
// triggers once at every 60 milliseconds
$picture.addEventListener(
'mousemove', throttle(handleMoveEvent, 60)
)

This should be the final result of this project, a complete, interactive and super cool SVG super hero animation! I hope you enjoyed it and learned a bit about the superpowers of SVGs ?.

The post Superhero Animation Effect with SVG Filters appeared first on Codrops.