Commit 8587df5c authored by Robert Knight's avatar Robert Knight

Implement a fallback if CSS blending is not supported

Implement a fallback for browsers that don't support the CSS
`mix-blend-mode` property (IE 11, Edge < 79) by merging overlapping
highlights into a single layer with uniform opacity. This prevents
overlapping highlights from affecting readability since highlights are
blended with the content underneath using normal blending in this case.
parent 55e4ad5a
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
function isCSSPropertySupported(property, value) {
if (typeof CSS !== 'function' || typeof CSS.supports !== 'function') {
/* istanbul ignore next */
return false;
}
return CSS.supports(property, value);
}
/**
* Polyfill for `element.closest(selector)`, only needed for IE 11.
*/
......@@ -73,6 +81,11 @@ function drawHighlightsAbovePdfCanvas(highlightEl) {
'.hypothesis-highlight-layer'
);
const isCssBlendSupported = isCSSPropertySupported(
'mix-blend-mode',
'multiply'
);
if (!svgHighlightLayer) {
// Create SVG layer. This must be in the same stacking context as
// the canvas so that CSS `mix-blend-mode` can be used to control how SVG
......@@ -91,15 +104,20 @@ function drawHighlightsAbovePdfCanvas(highlightEl) {
svgStyle.width = '100%';
svgStyle.height = '100%';
if (isCssBlendSupported) {
// Use multiply blending so that highlights drawn on top of text darken it
// rather than making it lighter. This improves contrast and thus readability
// of highlighted text. This choice optimizes for dark text on a light
// background, as the most common case.
// of highlighted text, especially for overlapping highlights.
//
// Browsers which don't support the `mix-blend-mode` property (IE 11, Edge < 79)
// will use "normal" blending, which is still usable but has reduced contrast,
// especially for overlapping highlights.
// This choice optimizes for the common case of dark text on a light background.
svgStyle.mixBlendMode = 'multiply';
} else {
// For older browsers (IE 11, Edge < 79) we draw all the highlights as
// opaque and then make the entire highlight layer transparent. This means
// that there is no visual indication of whether text has one or multiple
// highlights, but it preserves readability.
svgStyle.opacity = 0.3;
}
}
const canvasRect = canvasEl.getBoundingClientRect();
......@@ -111,7 +129,13 @@ function drawHighlightsAbovePdfCanvas(highlightEl) {
rect.setAttribute('y', highlightRect.top - canvasRect.top);
rect.setAttribute('width', highlightRect.width);
rect.setAttribute('height', highlightRect.height);
if (isCssBlendSupported) {
rect.setAttribute('class', 'hypothesis-svg-highlight');
} else {
rect.setAttribute('class', 'hypothesis-svg-highlight is-opaque');
}
svgHighlightLayer.appendChild(rect);
return rect;
......
......@@ -221,6 +221,53 @@ describe('annotator/highlighter', () => {
assert.isNull(container.querySelector('rect'));
assert.notOk(highlight.svgHighlight);
});
describe('CSS blend mode support testing', () => {
beforeEach(() => {
sinon.stub(CSS, 'supports');
});
afterEach(() => {
CSS.supports.restore();
});
it('renders highlights when mix-blend-mode is supported', () => {
const container = document.createElement('div');
render(<PdfPage />, container);
CSS.supports.withArgs('mix-blend-mode', 'multiply').returns(true);
highlightPdfRange(container);
// When mix blending is available, the highlight layer has default
// opacity and highlight rects are transparent.
const highlightLayer = container.querySelector(
'.hypothesis-highlight-layer'
);
assert.equal(highlightLayer.style.opacity, '');
const rect = container.querySelector('rect');
assert.equal(rect.getAttribute('class'), 'hypothesis-svg-highlight');
});
it('renders highlights when mix-blend-mode is not supported', () => {
const container = document.createElement('div');
render(<PdfPage />, container);
CSS.supports.withArgs('mix-blend-mode', 'multiply').returns(false);
highlightPdfRange(container);
// When mix blending is not available, highlight rects are opaque and
// the entire highlight layer is transparent.
const highlightLayer = container.querySelector(
'.hypothesis-highlight-layer'
);
assert.equal(highlightLayer.style.opacity, '0.3');
const rect = container.querySelector('rect');
assert.include(
rect.getAttribute('class'),
'hypothesis-svg-highlight is-opaque'
);
});
});
});
});
......
......@@ -27,6 +27,10 @@
.hypothesis-highlights-always-on {
.hypothesis-svg-highlight {
fill: var.$highlight-color;
&.is-opaque {
fill: yellow;
}
}
.hypothesis-highlight {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment