This commit is contained in:
Joel Rainwater 2022-10-26 19:08:23 +02:00 committed by GitHub
commit 7ecf4f2fa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 290 additions and 31 deletions

View File

@ -46,6 +46,7 @@ Below is a list of all the supported CSS properties and values.
- max-width
- min-height
- min-width
- mix-blend-mode (**Limited support**)
- opacity
- overflow
- overflow-wrap
@ -79,7 +80,6 @@ These CSS properties are **NOT** currently supported
- [box-shadow](https://github.com/niklasvh/html2canvas/pull/1086)
- [filter](https://github.com/niklasvh/html2canvas/issues/493)
- [font-variant-ligatures](https://github.com/niklasvh/html2canvas/pull/1085)
- [mix-blend-mode](https://github.com/niklasvh/html2canvas/issues/580)
- [object-fit](https://github.com/niklasvh/html2canvas/issues/1064)
- [repeating-linear-gradient()](https://github.com/niklasvh/html2canvas/issues/1162)
- [writing-mode](https://github.com/niklasvh/html2canvas/issues/1258)

174
package-lock.json generated
View File

@ -5,7 +5,8 @@
"requires": true,
"packages": {
"": {
"version": "1.4.0",
"name": "html2canvas",
"version": "1.4.1",
"license": "MIT",
"dependencies": {
"css-line-break": "^2.1.0",
@ -76,9 +77,9 @@
"standard-version": "^8.0.2",
"ts-jest": "^27.0.3",
"ts-loader": "^8.3.0",
"ts-node": "^10.1.0",
"ts-node": "^10.8.0",
"tslib": "^2.3.0",
"typescript": "^4.3.5",
"typescript": "^4.6.4",
"uglify-js": "^3.13.10",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^4.46.0",
@ -1430,6 +1431,18 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@eslint/eslintrc": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
@ -3313,6 +3326,31 @@
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.2",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz",
@ -3571,9 +3609,9 @@
"dev": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz",
"integrity": "sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"node_modules/@types/babel__core": {
@ -23258,32 +23296,33 @@
}
},
"node_modules/ts-node": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
"version": "10.8.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz",
"integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.1",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
@ -23299,6 +23338,27 @@
}
}
},
"node_modules/ts-node/node_modules/acorn": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ts-node/node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@ -23425,9 +23485,9 @@
}
},
"node_modules/typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@ -23828,6 +23888,12 @@
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
"dev": true
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"node_modules/v8-to-istanbul": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz",
@ -26032,6 +26098,15 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
"@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"requires": {
"@jridgewell/trace-mapping": "0.3.9"
}
},
"@eslint/eslintrc": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
@ -27713,6 +27788,28 @@
}
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.2",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz",
@ -27921,9 +28018,9 @@
"dev": true
},
"@tsconfig/node16": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz",
"integrity": "sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"@types/babel__core": {
@ -43593,23 +43690,38 @@
}
},
"ts-node": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
"version": "10.8.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz",
"integrity": "sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==",
"dev": true,
"requires": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.1",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"dependencies": {
"acorn": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"dev": true
},
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@ -43716,9 +43828,9 @@
}
},
"typescript": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true
},
"ua-parser-js": {
@ -44032,6 +44144,12 @@
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
"dev": true
},
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"v8-to-istanbul": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz",

View File

@ -87,9 +87,9 @@
"standard-version": "^8.0.2",
"ts-jest": "^27.0.3",
"ts-loader": "^8.3.0",
"ts-node": "^10.1.0",
"ts-node": "^10.8.0",
"tslib": "^2.3.0",
"typescript": "^4.3.5",
"typescript": "^4.6.4",
"uglify-js": "^3.13.10",
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^4.46.0",

View File

@ -41,6 +41,7 @@ import {listStyleImage} from './property-descriptors/list-style-image';
import {listStylePosition} from './property-descriptors/list-style-position';
import {listStyleType} from './property-descriptors/list-style-type';
import {marginBottom, marginLeft, marginRight, marginTop} from './property-descriptors/margin';
import {mixBlendMode} from './property-descriptors/mix-blend-mode';
import {overflow, OVERFLOW} from './property-descriptors/overflow';
import {overflowWrap} from './property-descriptors/overflow-wrap';
import {paddingBottom, paddingLeft, paddingRight, paddingTop} from './property-descriptors/padding';
@ -126,6 +127,7 @@ export class CSSParsedDeclaration {
marginRight: CSSValue;
marginBottom: CSSValue;
marginLeft: CSSValue;
mixBlendMode: ReturnType<typeof mixBlendMode.parse>;
opacity: ReturnType<typeof opacity.parse>;
overflowX: OVERFLOW;
overflowY: OVERFLOW;
@ -194,6 +196,7 @@ export class CSSParsedDeclaration {
this.marginRight = parse(context, marginRight, declaration.marginRight);
this.marginBottom = parse(context, marginBottom, declaration.marginBottom);
this.marginLeft = parse(context, marginLeft, declaration.marginLeft);
this.mixBlendMode = parse(context, mixBlendMode, declaration.mixBlendMode);
this.opacity = parse(context, opacity, declaration.opacity);
const overflowTuple = parse(context, overflow, declaration.overflow);
this.overflowX = overflowTuple[0];

View File

@ -0,0 +1,55 @@
import {IPropertyIdentValueDescriptor, PropertyDescriptorParsingType} from '../IPropertyDescriptor';
import {Context} from '../../core/context';
/**
* Guard function for checking validity of mode and casting as
* GlobalCompositeOperation if it's a valid value
*
* @param mode String value of the css mix-blend-mode
* @returns boolean
*/
function isGlobalCompositeOperation(mode: string): mode is GlobalCompositeOperation {
const globalCompositeModes = [
'color',
'color-burn',
'color-dodge',
'copy',
'darken',
'destination-atop',
'destination-in',
'destination-out',
'destination-over',
'difference',
'exclusion',
'hard-light',
'hue',
'lighten',
'lighter',
'luminosity',
'multiply',
'overlay',
'saturation',
'screen',
'soft-light',
'source-atop',
'source-in',
'source-out',
'source-over',
'xor'
];
return globalCompositeModes.indexOf(mode) !== -1;
}
export const mixBlendMode: IPropertyIdentValueDescriptor<GlobalCompositeOperation> = {
name: 'mix-blend-mode',
initialValue: 'normal',
prefix: false,
type: PropertyDescriptorParsingType.IDENT_VALUE,
parse: (_context: Context, mode: string) => {
if (isGlobalCompositeOperation(mode)) {
return mode;
}
// This is the default in css
return 'source-over';
}
};

1
src/global.d.ts vendored
View File

@ -2,6 +2,7 @@ interface CSSStyleDeclaration {
textDecorationColor: string;
textDecorationLine: string;
overflowWrap: string;
mixBlendMode: string;
}
interface DocumentType extends Node, ChildNode {

View File

@ -189,6 +189,7 @@ export class CanvasRenderer extends Renderer {
switch (paintOrderLayer) {
case PAINT_ORDER_LAYER.FILL:
this.ctx.fillStyle = asString(styles.color);
this.ctx.globalCompositeOperation = styles.mixBlendMode;
this.renderTextWithLetterSpacing(text, styles.letterSpacing, baseline);
const textShadows: TextShadow = styles.textShadow;
@ -276,6 +277,7 @@ export class CanvasRenderer extends Renderer {
this.path(path);
this.ctx.save();
this.ctx.clip();
this.ctx.globalCompositeOperation = container.styles.mixBlendMode;
this.ctx.drawImage(
image,
0,
@ -714,6 +716,7 @@ export class CanvasRenderer extends Renderer {
if (!isTransparent(styles.backgroundColor)) {
this.ctx.fillStyle = asString(styles.backgroundColor);
this.ctx.globalCompositeOperation = styles.mixBlendMode;
this.ctx.fill();
}

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<title>Mix Blend Mode tests</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="../test.js"></script>
<style>
.circle {
width: 80px;
height: 80px;
border-radius: 50%;
position: absolute;
}
.circle-1 {
background: red;
}
.circle-2 {
background: green;
left: 40px;
}
.circle-3 {
background: skyblue;
left: 20px;
top: 40px;
}
.container {
position: relative;
border: 1px solid purple;
margin-bottom: 5px;
}
.bg-image {
position: relative;
background-image: url('../assets/image.jpg');
width: 120px;
height: 120px;
}
</style>
</head>
<body>
<div class="container">
<p>Green circle: mix-blend-mode: screen</p>
<p>Blue circle: mix-blend-mode: color-burn</p>
<div style="height: 120px; position: relative; background-color: gray;">
<div class="circle circle-1"></div>
<div class="circle circle-2" style="mix-blend-mode: screen"></div>
<div class="circle circle-3" style="mix-blend-mode: color-burn"></div>
</div>
</div>
<div class="container">
<p>Red background with mix-blend-mode: overlay over a background image</p>
<div class="bg-image">
<div
style="
position: absolute;
height: 100%;
width: 100%;
background-color: red;
mix-blend-mode: overlay;
"
></div>
</div>
</div>
<div class="container" style="background-color: green">
<p>Image with mix-blend-mode: multiply</p>
<img src="../assets/image2.jpg" style="mix-blend-mode: multiply" />
</div>
<div class="container" style="height: 120px; background-color: white;">
<div class="circle" style="position: relative; display: inline-block; background-color: red"></div>
<div class="circle" style="position: relative; display: inline-block; background-color: blue"></div>
<div class="circle" style="position: relative; display: inline-block; background-color: green"></div>
<div class="circle" style="position: relative; display: inline-block; background-color: yellow"></div>
<div class="circle" style="position: relative; display: inline-block; background-color: darkblue"></div>
<div class="circle" style="position: relative; display: inline-block; background-image: url('../assets/image2_1.jpg')"></div>
<p style="mix-blend-mode: color; font-weight: 900; font-size: x-large; position: absolute; top: 0;">
mix-blend-mode: color; Large text over background colors.
</p>
</div>
</body>
</html>