I took Panayiotis Terzis's RISO Printing: Zines and Small Publishing class at RisoLAB this last summer. Our first assignment was to submit artwork that could be used in a class zine, and we were asked to use 3 colors with at least some of the colors overlapping. No other requirements.
I did a farily quick p5.js sketch to assemble a design similar to what you see below. But what's actually below is several of the variations the app produced. Coming across these, I thought it might be good fodder for my first mini-project using Framer Motion. This is super simple, but I'm happy to have this raw material to do this little exploration.
Pretty basic:
"use client";
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import Image from "next/image";
const ImageFader = ({ images, interval = 3000 }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const [isForward, setIsForward] = useState(true);
const duration = interval * 0.95 / 1000
useEffect(() => {
const timer = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length);
setIsForward(true);
}, interval);
return () => clearInterval(timer);
}, [images, interval]);
return (
<div className="relative w-full mb-16">
<AnimatePresence initial={false} custom={isForward}>
<motion.div
key={currentIndex}
custom={isForward}
variants={{
enter: () => ({
opacity: 0,
}),
center: {
opacity: 1,
zIndex: 1,
},
exit: () => ({
opacity: 0,
zIndex: 0,
}),
}}
initial="enter"
animate="center"
exit="exit"
transition={{
opacity: { duration: duration },
}}
className="absolute inset-0"
>
<Image
src={images[currentIndex]}
alt={`Image ${currentIndex + 1}`}
width={1024}
height={791}
/>
</motion.div>
</AnimatePresence>
</div>
);
};
export default ImageFader;
The p5.js sketch is probably more interesting.
The colors you see below are the colors that are available at RisoLAB. There are two systems with their own sets of colors. There are methods for working in Photoshop in order to approximate those colors while you're constructing your artwork.
"use client";
import React, { useEffect, useState } from "react";
import { NextReactP5Wrapper } from "@p5-wrapper/next";
import dynamic from "next/dynamic";
const p5 = dynamic(() => import("p5"), { ssr: false });
const p5svg = dynamic(() => import("p5.js-svg"), { ssr: false });
const classZine = (p) => {
const colorsOpacity = 180;
const circSize = 120;
const systemOneColors = [
// [0, 0, 0, colorsOpacity, 1], // black
[0, 120, 191, colorsOpacity, 1], // blue
[98, 168, 229, colorsOpacity, 1], // cornflower
[0, 157, 165, colorsOpacity, 1], // light teal
[0, 169, 92, colorsOpacity, 1], // green
[247, 255, 0, colorsOpacity, 1], // yellow
[255, 108, 47, colorsOpacity, 1], // orange
[241, 80, 96, colorsOpacity, 1], // bright red
[255, 72, 176, colorsOpacity, 1], // fluorescent pink
[157, 122, 210, colorsOpacity, 1], // violet
[172, 147, 110, colorsOpacity, 1], // metallic gold
[255, 255, 255, colorsOpacity, 1], // white
];
const systemTwoColors = [
[0, 0, 0, colorsOpacity, 1], // black
[73, 130, 207, colorsOpacity, 1], // sky blue
[94, 200, 229, colorsOpacity, 1], // aqua
[227, 237, 85, colorsOpacity, 1], // light lime
[247, 255, 0, colorsOpacity, 1], // Yellow
[246, 80, 88, colorsOpacity, 1], // scarlet
[255, 72, 176, colorsOpacity, 1], // fluorescent pink
[172, 147, 110, colorsOpacity, 1], // metallic gold
[255, 255, 255, 255, 1], // white
];
const chosen = [
[73, 130, 207, colorsOpacity, 2], // sky blue
[94, 200, 229, colorsOpacity, 3], // aqua
[246, 80, 88, colorsOpacity, 1], // scarlet
[255, 255, 255, 255, 2], // white
];
// Take the 5th element of the color array and return
// a new array with the colors repeated that many times
//
const repeatedColors = chosen.reduce((acc, [r, g, b, a, times]) => {
return acc.concat(Array(times).fill([r, g, b, a]));
}, []);
const colorEmployed = repeatedColors;
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};
const gridCirc = (numCols, numRows, noFill) => {
const cols = numCols;
const rows = numRows;
const totalWidth = circSize * (cols - 1);
const totalHeight = circSize * (rows - 1);
for (let x = 0; x < cols; x++) {
for (let y = 0; y < rows; y++) {
p.push();
p.translate(
p.map(
x,
0,
cols - 1,
p.width / 2 - totalWidth / 2,
p.width / 2 + totalWidth / 2
),
p.map(
y,
0,
rows - 1,
p.height / 2 - totalHeight / 2,
p.height / 2 + totalHeight / 2
),
0
);
const colorArray = shuffleArray(colorEmployed);
const [r, g, b, a] = colorArray;
p.fill(r, g, b, a);
p.stroke(0);
if (noFill) {
p.noFill();
}
p.ellipse(0, 0, circSize, circSize);
p.pop();
}
}
};
p.setup = () => {
const cnv = p.createCanvas(1280, 989, p.SVG);
p.noLoop();
p.pixelDensity(3);
p.keyPressed = () => {
if (p.key === "s") {
const date = new Date();
const timestamp = date.toISOString().replace(/[:.]/g, "-").slice(0, -5);
p.save(cnv, `class-zine-${timestamp}.png`);
}
};
p.keyPressed = () => {
if (p.key === "p") {
// exportPdf(cnv);
}
};
};
p.draw = () => {
p.background(255);
p.strokeWeight(1);
p.noFill();
p.strokeWeight(2);
gridCirc(10, 8, false);
gridCirc(9, 7, false);
gridCirc(10, 8, true);
};
};
export default function ClassZine() {
const [refreshKey, setRefreshKey] = useState(true);
const refreshSketch = () => {
setRefreshKey((currentValue) => !currentValue);
};
return (
<div>
<button className="refresh-sketch" onClick={refreshSketch}>
Refresh Sketch
</button>
<NextReactP5Wrapper sketch={(p) => classZine(p, refreshKey)} />
</div>
);
}