Skip to content

Commit b9cc18f

Browse files
committed
Merge branch 'master' into gh-pages
2 parents 407945d + 79a5792 commit b9cc18f

File tree

6 files changed

+220
-10
lines changed

6 files changed

+220
-10
lines changed

.github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
env:
2727
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
2828
- uses: actions/upload-artifact@v3
29+
if: ${{ failure() }}
2930
with:
3031
name: test-screenshots
3132
path: shots/

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ You can then `require('canvas-confetti');` to use it in your project build. _Not
3030
You can also include this library in your HTML page directly from a CDN:
3131

3232
```html
33-
<script src="https://cdn.jsdelivr.net/npm/[email protected].1/dist/confetti.browser.min.js"></script>
33+
<script src="https://cdn.jsdelivr.net/npm/[email protected].2/dist/confetti.browser.min.js"></script>
3434
```
3535

3636
_Note: you should use the latest version at the time that you include your project. You can see all versions [on the releases page](https://github.com/catdad/canvas-confetti/releases)._

fixtures/debug.html

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Debug emoji confetti</title>
7+
</head>
8+
<body>
9+
<script>
10+
// this page is a demo that is not built, so fudge the module.exports support
11+
// define a global `module` so that the actual source file can use it
12+
window.module = {};
13+
</script>
14+
<script src="../src/confetti.js"></script>
15+
<script>
16+
// define the `module.exports` as the `confetti` global, the way that the
17+
// cdn distributed file would
18+
window.confetti = module.exports;
19+
</script>
20+
21+
<p><button id="test-confetti">Emoji confetti test</button></p>
22+
<canvas id="debug-canvas" width="600" height="650" style="outline: 1px solid red"></canvas>
23+
<p id="test-text"></p>
24+
25+
<script>
26+
const loadFonts = async () => {
27+
const isSafari = navigator.vendor === 'Apple Computer, Inc.';
28+
const fontName = 'Noto Color Emoji';
29+
30+
await Promise.all([0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(n => {
31+
const safari = `url(https://fonts.gstatic.com/s/notocoloremoji/v25/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabts6diysYTngZPnMC1MfLd4hQ.${n}.woff2)`;
32+
const realBrowser = `url(https://fonts.gstatic.com/s/notocoloremoji/v25/Yq6P-KqIXTD0t4D9z1ESnKM3-HpFabsE4tq3luCC7p-aXxcn.${n}.woff2)`;
33+
34+
const fontFile = new FontFace(
35+
fontName,
36+
isSafari ? safari : realBrowser
37+
);
38+
39+
document.fonts.add(fontFile);
40+
41+
return fontFile.load();
42+
}));
43+
44+
return `"${fontName}"`;
45+
};
46+
47+
const connectTest = ({ fontFamily }) => {
48+
const button = document.getElementById('test-confetti');
49+
50+
const scalar = 3;
51+
52+
// make this a getter so that the shapes don't initialize on page load
53+
const getShapes = ((shapes) => () => {
54+
if (shapes.length) {
55+
return shapes;
56+
}
57+
58+
shapes = [
59+
['🦄', '🍑', '🤣', '🐈', 'bg'].map(text => confetti.shapeFromText({ text, scalar })),
60+
['🦄', '🍑', '🤣', '🐈', 'bg'].map(text => confetti.shapeFromText({ text, scalar, fontFamily }))
61+
];
62+
63+
return shapes;
64+
})([]);
65+
66+
button.onclick = () => {
67+
const [shapesDefault, shapesFont] = getShapes();
68+
69+
confetti({
70+
particleCount: 10,
71+
shapes: shapesDefault,
72+
scalar,
73+
origin: { y: 0.7, x: 0.25 }
74+
});
75+
confetti({
76+
particleCount: 10,
77+
shapes: shapesFont,
78+
scalar,
79+
origin: { y: 0.7, x: 0.75 }
80+
});
81+
};
82+
};
83+
84+
const drawBitmapToCanvas = (bitmap) => {
85+
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
86+
const ctx = canvas.getContext('2d');
87+
88+
ctx.drawImage(bitmap, 0, 0);
89+
90+
return canvas;
91+
};
92+
93+
const drawTransformedEmoji = async ({ fontFamily, canvas, ctx, offsetX = 0, offsetY = 0 }) => {
94+
['🦄', '🍑', '🤣', '🐈'].forEach((text, idx) => {
95+
const shape1 = confetti.shapeFromText({ text, scalar: 1, fontFamily });
96+
const shape2 = confetti.shapeFromText({ text, scalar: 2, fontFamily });
97+
const shape5 = confetti.shapeFromText({ text, scalar: 5, fontFamily });
98+
99+
const y = idx * 100 + 50 + offsetY;
100+
101+
ctx.drawImage(shape1.bitmap, 0, y);
102+
ctx.drawImage(shape2.bitmap, 100, y);
103+
ctx.drawImage(shape5.bitmap, 200, y);
104+
105+
[[shape1, 1], [shape2, 2], [shape5, 5]].forEach(([shape, scale], j) => {
106+
const x = 300 + (j * 100) + offsetX;
107+
108+
const rotation = 3.2;
109+
const scaleX = scale * 0.8;
110+
const scaleY = scale * 1.4;
111+
112+
var matrix = new DOMMatrix([
113+
Math.cos(rotation) * scaleX,
114+
Math.sin(rotation) * scaleX,
115+
-Math.sin(rotation) * scaleY,
116+
Math.cos(rotation) * scaleY,
117+
x,
118+
y
119+
]);
120+
matrix.multiplySelf(new DOMMatrix(shape.matrix));
121+
122+
const pattern = (() => {
123+
try {
124+
// most browsers support this, it's spec
125+
return ctx.createPattern(shape.bitmap, 'no-repeat');
126+
} catch (e) {
127+
// safari doesn't, because of course it doesn't
128+
// so draw the bitmap to a canvas first and create a
129+
// pattern from that canvas
130+
console.log('failed to create bitmap pattern:', e);
131+
return ctx.createPattern(drawBitmapToCanvas(shape.bitmap), 'no-repeat');
132+
}
133+
})();
134+
135+
pattern.setTransform(matrix);
136+
137+
ctx.fillStyle = pattern;
138+
ctx.fillRect(x - 100, y - 100, 300, 300);
139+
});
140+
});
141+
};
142+
143+
const drawDebugEmoji = async ({ fontFamily, canvas, ctx, offsetX = 0, offsetY = 0 }) => {
144+
const text = '🦄';
145+
146+
const draw = ({ offsetX, offsetY, fontFamily }) => {
147+
const opts = { text, scalar: 15 };
148+
149+
if (fontFamily) {
150+
opts.fontFamily = fontFamily;
151+
}
152+
153+
const shape = confetti.shapeFromText(opts);
154+
155+
ctx.drawImage(shape.bitmap, offsetX, offsetY);
156+
157+
ctx.lineWidth = 1;
158+
ctx.strokeStyle = 'orange';
159+
ctx.strokeRect(offsetX, offsetY, shape.bitmap.width, shape.bitmap.height);
160+
161+
return { width: shape.bitmap.width, height: shape.bitmap.height };
162+
};
163+
164+
const { width, height: height1 } = draw({ offsetX: 10 + offsetX, offsetY: 10 + offsetY });
165+
const { height: height2 } = draw({ offsetX: 20 + width + offsetX, offsetY: 10 + offsetY, fontFamily });
166+
167+
return Math.max(height1, height2);
168+
};
169+
170+
const renderTestText = ({ fontFamily }) => {
171+
const fontSize = '20px';
172+
const text = document.getElementById('test-text');
173+
174+
const withFont = document.createElement('div');
175+
Object.assign(withFont.style, { fontFamily, fontSize });
176+
withFont.appendChild(document.createTextNode(`plain text in ${fontFamily}: 🦄 🍑 🤣`));
177+
178+
const withSystemUI = document.createElement('div');
179+
Object.assign(withSystemUI.style, { fontFamily: '"system ui"', fontSize });
180+
withSystemUI.appendChild(document.createTextNode(`plain text in "system ui": 🦄 🍑 🤣`));
181+
182+
text.appendChild(withFont);
183+
text.appendChild(withSystemUI);
184+
};
185+
186+
Promise.resolve().then(async () => {
187+
// const fontFamily = null;
188+
const fontFamily = await loadFonts();
189+
190+
const canvas = document.querySelector('#debug-canvas');
191+
const ctx = canvas.getContext('2d');
192+
193+
renderTestText({ fontFamily });
194+
195+
connectTest({ fontFamily, canvas, ctx });
196+
197+
await drawDebugEmoji({ fontFamily, canvas, ctx });
198+
await drawTransformedEmoji({ fontFamily, canvas, ctx, offsetY: 200 });
199+
}).catch(e => {
200+
console.log('something went wrong:', e);
201+
});
202+
</script>
203+
</body>
204+
</html>

package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "canvas-confetti",
3-
"version": "1.9.1",
3+
"version": "1.9.2",
44
"description": "performant confetti animation in the browser",
55
"main": "src/confetti.js",
66
"module": "dist/confetti.module.mjs",
@@ -31,15 +31,14 @@
3131
"babel-eslint": "^8.2.1",
3232
"browserify": "^15.2.0",
3333
"cross-env": "^5.1.3",
34-
"eslint": "^4.16.0",
34+
"eslint": "^6.8.0",
3535
"eslint-plugin-ava": "9.0.0",
3636
"jimp": "^0.2.28",
3737
"puppeteer": "^19.11.1",
3838
"rootrequire": "^1.0.0",
3939
"send": "^0.16.1",
4040
"terser": "^3.14.1"
4141
},
42-
"dependencies": {},
4342
"keywords": [
4443
"canvas",
4544
"confetti",

src/confetti.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@
808808
scalar = 1,
809809
color = '#000000',
810810
// see https://nolanlawson.com/2022/04/08/the-struggle-of-using-native-emoji-on-the-web/
811-
fontFamily = '"Twemoji Mozilla", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", "system emoji", sans-serif';
811+
fontFamily = '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", "Twemoji Mozilla", "system emoji", sans-serif';
812812

813813
if (typeof textData === 'string') {
814814
text = textData;
@@ -829,15 +829,21 @@
829829

830830
ctx.font = font;
831831
var size = ctx.measureText(text);
832-
var width = Math.floor(size.width);
833-
var height = Math.floor(size.fontBoundingBoxAscent + size.fontBoundingBoxDescent);
832+
var width = Math.ceil(size.actualBoundingBoxRight + size.actualBoundingBoxLeft);
833+
var height = Math.ceil(size.actualBoundingBoxAscent + size.actualBoundingBoxDescent);
834+
835+
var padding = 2;
836+
var x = size.actualBoundingBoxLeft + padding;
837+
var y = size.actualBoundingBoxAscent + padding;
838+
width += padding + padding;
839+
height += padding + padding;
834840

835841
canvas = new OffscreenCanvas(width, height);
836842
ctx = canvas.getContext('2d');
837843
ctx.font = font;
838844
ctx.fillStyle = color;
839845

840-
ctx.fillText(text, 0, fontSize);
846+
ctx.fillText(text, x, y);
841847

842848
var scale = 1 / scalar;
843849

test/index.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -739,8 +739,8 @@ test('[text] shapeFromText renders an emoji', async t => {
739739
...shape
740740
}, {
741741
type: 'bitmap',
742-
matrix: [ 0.1, 0, 0, 0.1, -6.25, -5.8500000000000005 ],
743-
hash: '8647FpWTCBH'
742+
matrix: [ 0.1, 0, 0, 0.1, -5.7, -5.550000000000001 ],
743+
hash: 'c4y5z8b83AC'
744744
});
745745
});
746746

0 commit comments

Comments
 (0)