Skip to content

Commit

Permalink
1.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
davay42 committed Feb 6, 2022
1 parent 8cf5948 commit e8aaf4d
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 54 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# gun-avatar changelog

## 1.6.0

BREAKING CHANGE! Now the `gunAvatar()` function gets an options object instead of a line of params.

## 1.5.0

Proper custom elements

## 1.4.1

You can specify the avatar class or element with the only function argument.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ After you add the script to the page you get a custom element `<gun-avatar />` f
size="300"
round
dark
draw="circles"
/>
```

Expand All @@ -52,6 +53,7 @@ Add the script to the page and then add `gun-avatar` class to an img tag along w
<img
class="gun-avatar"
data-size="200"
data-draw="squares"
data-pub="YZOBPSkw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8"
/>
```
Expand All @@ -60,7 +62,7 @@ You can set up a custom class name with `mountClass('avatar')`

### 3. JS function

Install the `gun-avatar` package and import the `gunAvatar` function. Then you can use it to generate the base64 string to place into the src attribute with your favourite library or vanilla js. Function get two parameters: `pub` and `size` in px.
Install the `gun-avatar` package and import the `gunAvatar` function. Then you can use it to generate the base64 string to place into the src attribute with your favourite library or vanilla js. Function gets an object with options: `pub` , `size` in px, `draw` mode, `dark` of not, `reflect` or not.

```javascript
import { gunAvatar } from "https://cdn.skypack.dev/gun-avatar";
Expand All @@ -69,10 +71,27 @@ const pub =
"YZOBPSkw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8";

document.addEventListener("DOMContentLoaded", () => {
document.getElementById("avatar").src = gunAvatar(pub, 200);
document.getElementById("avatar").src = gunAvatar({ pub, size: 200 });
});
```

### MODES

1. **Circles** - the default mode.

2. **Squares** - gradient squares over blurred ones (useful for rooms)

![rooms](https://raw.githubusercontent.com/DeFUCC/gun-avatar/master/rooms.gif)

```html
<gun-avatar
pub="0000000kw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8"
size="300"
reflect="false"
draw="squares"
></gun-avatar>
```

### ROAD MAP

- [x] make the mirroring canvas work in Safari
Expand All @@ -81,3 +100,6 @@ document.addEventListener("DOMContentLoaded", () => {
- [x] custom element mount
- [x] dark mode
- [x] editable class and element to mount
- [x] add more draw modes
- [x] `circles`
- [x] `squares`
23 changes: 17 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,26 @@ <h2>&lt;gun-avatar /&gt; custom element with reactive pub attribute</h2>
<p>Click on the avatar to generate a new SEA pub key</p>

<gun-avatar style="cursor:pointer" id="avatar"
pub="0000000kw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8" size="300" round />
pub="0000000kw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8" size="300" round ></gun-avatar>
</div>

<div>
<h4>img.gun-avatar with data-pub attribute</h4>
<h3>Room avatars</h3>
<gun-avatar style="cursor:pointer;" id="room"
pub="0000000kw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8" size="400" draw="squares" reflect="false" ></gun-avatar>
</div>

<img class="gun-avatar" data-size="100"
data-pub="YZOBPSkw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8">
<div>
<h4>Light and dark modes</h4>

<img class="gun-avatar" data-dark="true" data-size="50"
<img class="gun-avatar" data-size="200"
data-pub="YZOBPSkw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8">

<img class="gun-avatar" data-dark="true" data-size="200"
data-pub="YZOBPSkw75Ute2tFhdjDQgzR-GsGhlfSlZxgEZKuquI.2F-j9ItJY44U8vcRAsj-5lxnECG5TDyuPD8gEiuInp8" >
</div>

<script src="https://cdn.jsdelivr.net/npm/gun/sea.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gun/sea.js" type="module"></script>
<script type="module">
import {mountElement, mountClass} from './main.js'
mountElement()
Expand All @@ -41,6 +47,11 @@ <h4>img.gun-avatar with data-pub attribute</h4>
avatar.addEventListener('click', () => {
SEA.pair().then(p => avatar.setAttribute('pub', p.pub))
})
let room = document.getElementById('room')
SEA.pair().then(p => room.setAttribute('pub', p.pub))
room.addEventListener('click', () => {
SEA.pair().then(p => room.setAttribute('pub', p.pub))
})
</script>
<style>
.gun-avatar {
Expand Down
117 changes: 97 additions & 20 deletions main.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
const cache = {}; // stores already generated avatars

import "./patch.js";

export function mountClass(elClass = "gun-avatar") {
// add src base64 to every <img class="avatar" data-pub="YourUserPub">
document.addEventListener("DOMContentLoaded", () => {
let avatars = document.getElementsByClassName(elClass);
for (let img of avatars) {
img.src = gunAvatar(img.dataset.pub, img.dataset.size, img.dataset.dark);
img.src = gunAvatar({
pub: img.dataset.pub,
size: img.dataset.size,
dark: img.dataset.dark,
draw: img.dataset.draw,
reflect: img.dataset.reflect != "false",
});
}
});
}
Expand All @@ -29,6 +33,12 @@ export function mountElement(elName = "gun-avatar") {
? this.getAttribute("pub")
: "1234123455Ute2tFhdjDQgzR-1234lfSlZxgEZKuquI.2F-j1234434U1234Asj-5lxnECG5TDyuPD8gEiuI123";
this.size = this.hasAttribute("size") ? this.getAttribute("size") : 400;
this.draw = this.hasAttribute("draw")
? this.getAttribute("draw")
: "circles";
this.reflect = this.hasAttribute("reflect")
? this.getAttribute("reflect") != "false"
: true;
this.round =
this.hasAttribute("round") || this.getAttribute("round") == ""
? true
Expand All @@ -42,13 +52,19 @@ export function mountElement(elName = "gun-avatar") {
this.hasAttribute("dark") || this.getAttribute("dark") == ""
? true
: false;
this.img.src = gunAvatar(this.pub, this.size, this.dark);
this.img.src = gunAvatar({
pub: this.pub,
size: this.size,
dark: this.dark,
draw: this.draw,
reflect: this.reflect,
});
}
connectedCallback() {
this.render();
}
static get observedAttributes() {
return ["pub", "round", "size", "dark"];
return ["pub", "round", "size", "dark", "draw", "reflect"];
}
attributeChangedCallback(name, oldValue, newValue) {
this.render();
Expand All @@ -60,11 +76,20 @@ export function mountElement(elName = "gun-avatar") {
initiated = true;
}

const cache = {}; // stores already generated avatars

// actual generator function, returns the base64 string

export function gunAvatar(pub, size = 800, dark = false) {
export function gunAvatar({
pub,
size = 800,
dark = false,
draw = "circles",
reflect = true,
} = {}) {
if (!pub) return;
if (cache?.[size]?.[pub]) return cache[size][pub];
if (cache?.[draw]?.[size]?.[pub])
return cache[dark ? "dark" : "light"][draw][size][pub];

const canvas = document.createElement("canvas");
canvas.width = canvas.height = size;
Expand All @@ -74,30 +99,81 @@ export function gunAvatar(pub, size = 800, dark = false) {
const decoded = split.map((single) => decodeUrlSafeBase64(single));

drawGradient(ctx, decoded[0][42], decoded[1][42], size, dark);
drawCircles(decoded[0], ctx, size, 0.42 * size);
ctx.globalCompositeOperation = "lighter";
drawCircles(decoded[1], ctx, size, 0.125 * size);

ctx.globalCompositeOperation = "source-over";
ctx.scale(-1, 1);
ctx.translate(-size / 2, 0);
ctx.drawImage(canvas, size / 2, 0, size, size, 0, 0, size, size);
cache[size] = cache[size] || {};
cache[size][pub] = canvas.toDataURL();
return cache[size][pub];
if (draw == "circles") {
drawCircles(decoded[0], ctx, size, 0.42 * size);
ctx.globalCompositeOperation = "multiply";
drawCircles(decoded[1], ctx, size, 0.125 * size);
}

if (draw == "squares") {
ctx.filter = "blur(20px)";
drawSquares(decoded[0], ctx, size);
ctx.filter = "blur(0px)";
ctx.globalCompositeOperation = "color-burn";
drawSquares(decoded[1], ctx, size);
}
if (reflect) {
ctx.globalCompositeOperation = "source-over";
ctx.scale(-1, 1);
ctx.translate(-size / 2, 0);
ctx.drawImage(canvas, size / 2, 0, size, size, 0, 0, size, size);
}
let mode = dark ? "dark" : "light";
cache[mode] = cache[mode] || {};
cache[mode][draw] = cache[mode][draw] || {};
cache[mode][draw][size] = cache[mode][draw][size] || {};
cache[mode][draw][size][pub] = canvas.toDataURL();
return cache[mode][draw][size][pub];
}

// FUNCTIONS

function drawGradient(ctx, top, bottom, size, dark = false) {
var gradient = ctx.createLinearGradient(0, 0, 0, size);
let offset = 70;
if (dark) offset = 0;
gradient.addColorStop(0, `hsl(0,0%,${offset + top * 30}%)`);
gradient.addColorStop(1, `hsl(0,0%,${offset + bottom * 30}%)`);
gradient.addColorStop(0, `hsla(0,0%,${offset + top * 30}%)`);
gradient.addColorStop(1, `hsla(0,0%,${offset + bottom * 30}%)`);

ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size, size);
}

function drawSquares(data, ctx, size) {
const chunks = chunkIt(data, 14);
chunks.forEach((chunk) => {
if (chunk.length == 14) {
let x = chunk[0] * size;
let y = chunk[1] * size;
let r = size / 8 + chunk[2] * size * (7 / 8);
let angle = chunk[13] * Math.PI;
let h1 = chunk[3] * 360;
let s1 = chunk[4] * 100;
let l1 = chunk[5] * 100;
let a1 = chunk[6];
let x1 = chunk[7];
let h2 = chunk[8] * 360;
let s2 = chunk[9] * 100;
let l2 = chunk[10] * 100;
let a2 = chunk[11];
let x2 = chunk[12];
const gradient = ctx.createLinearGradient(
x + r * x1,
0,
x + r * x2,
size
);
gradient.addColorStop(0, `hsla(${h1},${s1}%,${l1}%,${a1})`);
gradient.addColorStop(1, `hsla(${h2},${s2}%,${l2}%,${a2})`);
ctx.fillStyle = gradient;
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillRect(-r / 2, -r / 2, r, r);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
});
}

function drawCircles(data, ctx, size, radius) {
const chunks = chunkIt(data, 7);
chunks.forEach((chunk) => {
Expand Down Expand Up @@ -130,6 +206,7 @@ function decodeUrlSafeBase64(st) {
}
return arr;
}

function chunkIt(list, chunkSize = 3) {
return [...Array(Math.ceil(list.length / chunkSize))].map(() =>
list.splice(0, chunkSize)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"page": "vite build --mode page",
"serve": "vite preview --port=5005",
"deploy": "sh deploy.sh"
},
"devDependencies": {
"vite": "^2.5.6"
"vite": "^2.7.13"
}
}
26 changes: 13 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions test.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@

<body>

<script src="https://unpkg.com/gun-avatar"></script>
<script type="module">
import {mount} from 'gun-avatar'
import {mount} from 'https://unpkg.com/gun-avatar'
mount()
</script>
<img class="gun-avatar" data-size="200"
Expand Down
Loading

0 comments on commit e8aaf4d

Please sign in to comment.