Jestem w sumie n00b z HTML5
i pracuję z canvas
renderowanie kształty, kolory i tekst. W mojej aplikacji mam adapter widoku, który dynamicznie tworzy kanwę i wypełnia ją zawartością. Działa to naprawdę fajnie, poza tym, że mój tekst jest bardzo rozmyty / rozmyty / rozciągnięty. Widziałem wiele innych postów wyjaśniających, dlaczego zdefiniowanie szerokości i wysokości w programie CSS
spowoduje ten problem, ale definiuję to wszystko w javascript
.
Odpowiedni kod (zobacz Fiddle ):
var width = 500;//FIXME:size.w;
var height = 500;//FIXME:size.h;
var canvas = document.createElement("canvas");
//canvas.className="singleUserCanvas";
canvas.width=width;
canvas.height=height;
canvas.border = "3px solid #999999";
canvas.bgcolor = "#999999";
canvas.margin = "(0, 2%, 0, 2%)";
var context = canvas.getContext("2d");
//////////////////
//// SHAPES ////
//////////////////
var left = 0;
//draw zone 1 rect
context.fillStyle = "#8bacbe";
context.fillRect(0, (canvas.height*5/6)+1, canvas.width*1.5/8.5, canvas.height*1/6);
left = left + canvas.width*1.5/8.5;
//draw zone 2 rect
context.fillStyle = "#ffe381";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*2.75/8.5, canvas.height*1/6);
left = left + canvas.width*2.75/8.5 + 1;
//draw zone 3 rect
context.fillStyle = "#fbbd36";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);
left = left + canvas.width*1.25/8.5;
//draw target zone rect
context.fillStyle = "#004880";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*0.25/8.5, canvas.height*1/6);
left = left + canvas.width*0.25/8.5;
//draw zone 4 rect
context.fillStyle = "#f8961d";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width*1.25/8.5, canvas.height*1/6);
left = left + canvas.width*1.25/8.5 + 1;
//draw zone 5 rect
context.fillStyle = "#8a1002";
context.fillRect(left+1, (canvas.height*5/6)+1, canvas.width-left, canvas.height*1/6);
////////////////
//// TEXT ////
////////////////
//user name
context.fillStyle = "black";
context.font = "bold 18px sans-serif";
context.textAlign = 'right';
context.fillText("User Name", canvas.width, canvas.height*.05);
//AT:
context.font = "bold 12px sans-serif";
context.fillText("AT: 140", canvas.width, canvas.height*.1);
//AB:
context.fillText("AB: 94", canvas.width, canvas.height*.15);
//this part is done after the callback from the view adapter, but is relevant here to add the view back into the layout.
var parent = document.getElementById("layout-content");
parent.appendChild(canvas);
<div id="layout-content"></div>
Wyniki, które widzę (w Safari ) są znacznie bardziej wypaczone niż pokazane na skrzypcach:
Mój
Skrzypce
Co robię niepoprawnie? Czy potrzebuję osobnego płótna dla każdego elementu tekstowego? Czy to czcionka? Czy muszę najpierw zdefiniować płótno w układzie HTML5? Czy jest literówka? Zgubiłem się.
źródło
clearRect
.Odpowiedzi:
Element canvas działa niezależnie od współczynnika pikseli urządzenia lub monitora.
Na iPadzie 3+ współczynnik ten wynosi 2. Oznacza to, że obszar roboczy o szerokości 1000 pikseli musiałby teraz wypełnić 2000 pikseli, aby dopasować się do szerokości określonej na wyświetlaczu iPada. Na szczęście dla nas odbywa się to automatycznie przez przeglądarkę. Z drugiej strony jest to również powód, dla którego widać mniej definicji na obrazach i elementach płótna, które zostały wykonane tak, aby bezpośrednio pasowały do ich widocznego obszaru. Ponieważ Twoje płótno wie, jak wypełnić tylko 1000 pikseli, ale jest proszone o narysowanie do 2000 pikseli, przeglądarka musi teraz inteligentnie wypełnić puste miejsca między pikselami, aby wyświetlić element we właściwym rozmiarze.
Gorąco polecam przeczytanie tego artykułu z HTML5Rocks, który bardziej szczegółowo wyjaśnia, jak tworzyć elementy wysokiej rozdzielczości.
tl; dr? Oto przykład (oparty na powyższym tut), którego używam w moich własnych projektach, aby wypluć płótno o odpowiedniej rozdzielczości:
var PIXEL_RATIO = (function () { var ctx = document.createElement("canvas").getContext("2d"), dpr = window.devicePixelRatio || 1, bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; return dpr / bsr; })(); createHiDPICanvas = function(w, h, ratio) { if (!ratio) { ratio = PIXEL_RATIO; } var can = document.createElement("canvas"); can.width = w * ratio; can.height = h * ratio; can.style.width = w + "px"; can.style.height = h + "px"; can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0); return can; } //Create canvas with the device resolution. var myCanvas = createHiDPICanvas(500, 250); //Create canvas with a custom resolution. var myCustomCanvas = createHiDPICanvas(500, 200, 4);
Mam nadzieję że to pomoże!
źródło
Rozwiązany!
Postanowiłem sprawdzić, jakie zmiany ustawiłem w atrybutach szerokości i wysokości ,
javascript
aby zobaczyć, jak to wpłynęło na rozmiar płótna - i tak się nie stało. Zmienia rozdzielczość.Aby uzyskać pożądany wynik, musiałem również ustawić
canvas.style.width
atrybut, który zmienia fizyczny rozmiarcanvas
:canvas.width=1000;//horizontal resolution (?) - increase for better looking text canvas.height=500;//vertical resolution (?) - increase for better looking text canvas.style.width=width;//actual width of canvas canvas.style.height=height;//actual height of canvas
źródło
window.devicePixelRatio
i jest dobrze zaimplementowany w większości nowoczesnych przeglądarek.Zmieniam rozmiar elementu canvas poprzez css, aby zajął całą szerokość elementu nadrzędnego. Zauważyłem, że szerokość i wysokość mojego elementu nie są skalowane. Szukałem najlepszego sposobu na ustawienie odpowiedniego rozmiaru.
W ten prosty sposób Twoje płótno będzie idealnie ustawione, bez względu na to, jakiego ekranu użyjesz.
źródło
To w 100% rozwiązało to dla mnie:
var canvas = document.getElementById('canvas'); canvas.width = canvas.getBoundingClientRect().width; canvas.height = canvas.getBoundingClientRect().height;
(jest zbliżony do rozwiązania Adama Mańkowskiego).
źródło
Zauważyłem szczegół nie wymieniony w innych odpowiedziach. Rozdzielczość kanwy jest obcięta do wartości całkowitych.
Domyślne wymiary rozdzielczości obszaru roboczego to
canvas.width: 300
icanvas.height: 150
.Na moim ekranie
window.devicePixelRatio: 1.75
.Więc kiedy ustawiam
canvas.height = 1.75 * 150
wartość jest obcinana od żądanego262.5
do262
.Rozwiązaniem jest wybranie wymiarów układu CSS dla danego elementu,
window.devicePixelRatio
tak aby nie doszło do obcięcia przy skalowaniu rozdzielczości.Na przykład, mógłbym użyć
width: 300px
iheight: 152px
co dałoby liczby całkowite po pomnożeniu przez1.75
.Edycja : Innym rozwiązaniem jest wykorzystanie faktu, że piksele CSS mogą być ułamkowe, aby przeciwdziałać obcięciu skalowanych pikseli płótna.
Poniżej znajduje się demo wykorzystujące tę strategię.
Edycja : Oto skrzypce OP zaktualizowane do tej strategii: http://jsfiddle.net/65maD/83/ .
main(); // Rerun on window resize. window.addEventListener('resize', main); function main() { // Prepare canvas with properly scaled dimensions. scaleCanvas(); // Test scaling calculations by rendering some text. testRender(); } function scaleCanvas() { const container = document.querySelector('#container'); const canvas = document.querySelector('#canvas'); // Get desired dimensions for canvas from container. let {width, height} = container.getBoundingClientRect(); // Get pixel ratio. const dpr = window.devicePixelRatio; // (Optional) Report the dpr. document.querySelector('#dpr').innerHTML = dpr.toFixed(4); // Size the canvas a bit bigger than desired. // Use exaggeration = 0 in real code. const exaggeration = 20; width = Math.ceil (width * dpr + exaggeration); height = Math.ceil (height * dpr + exaggeration); // Set the canvas resolution dimensions (integer values). canvas.width = width; canvas.height = height; /*----------------------------------------------------------- - KEY STEP - Set the canvas layout dimensions with respect to the canvas resolution dimensions. (Not necessarily integer values!) -----------------------------------------------------------*/ canvas.style.width = `${width / dpr}px`; canvas.style.height = `${height / dpr}px`; // Adjust canvas coordinates to use CSS pixel coordinates. const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); } function testRender() { const canvas = document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); // fontBaseline is the location of the baseline of the serif font // written as a fraction of line-height and calculated from the top // of the line downwards. (Measured by trial and error.) const fontBaseline = 0.83; // Start at the top of the box. let baseline = 0; // 50px font text ctx.font = `50px serif`; ctx.fillText("Hello World", 0, baseline + fontBaseline * 50); baseline += 50; // 25px font text ctx.font = `25px serif`; ctx.fillText("Hello World", 0, baseline + fontBaseline * 25); baseline += 25; // 12.5px font text ctx.font = `12.5px serif`; ctx.fillText("Hello World", 0, baseline + fontBaseline * 12.5); }
/* HTML is red */ #container { background-color: red; position: relative; /* Setting a border will mess up scaling calculations. */ /* Hide canvas overflow (if any) in real code. */ /* overflow: hidden; */ } /* Canvas is green */ #canvas { background-color: rgba(0,255,0,.8); animation: 2s ease-in-out infinite alternate both comparison; } /* animate to compare HTML and Canvas renderings */ @keyframes comparison { 33% {opacity:1; transform: translate(0,0);} 100% {opacity:.7; transform: translate(7.5%,15%);} } /* hover to pause */ #canvas:hover, #container:hover > #canvas { animation-play-state: paused; } /* click to translate Canvas by (1px, 1px) */ #canvas:active { transform: translate(1px,1px) !important; animation: none; } /* HTML text */ .text { position: absolute; color: white; } .text:nth-child(1) { top: 0px; font-size: 50px; line-height: 50px; } .text:nth-child(2) { top: 50px; font-size: 25px; line-height: 25px; } .text:nth-child(3) { top: 75px; font-size: 12.5px; line-height: 12.5px; }
<!-- Make the desired dimensions strange to guarantee truncation. --> <div id="container" style="width: 313.235px; height: 157.122px"> <!-- Render text in HTML. --> <div class="text">Hello World</div> <div class="text">Hello World</div> <div class="text">Hello World</div> <!-- Render text in Canvas. --> <canvas id="canvas"></canvas> </div> <!-- Interaction instructions. --> <p>Hover to pause the animation.<br> Click to translate the green box by (1px, 1px).</p> <!-- Color key. --> <p><em style="color:red">red</em> = HTML rendered<br> <em style="color:green">green</em> = Canvas rendered</p> <!-- Report pixel ratio. --> <p>Device pixel ratio: <code id="dpr"></code> <em>(physical pixels per CSS pixel)</em></p> <!-- Info. --> <p>Zoom your browser to re-run the scaling calculations. (<code>Ctrl+</code> or <code>Ctrl-</code>)</p>
źródło
Lekko dostosowałem kod MyNameIsKo pod canvg (biblioteka SVG do Canvas js). Przez chwilę byłem zdezorientowany i spędziłem na tym trochę czasu. Mam nadzieję, że to komuś pomoże.
HTML
<div id="chart"><canvas></canvas><svg>Your SVG here</svg></div>
Javascript
window.onload = function() { var PIXEL_RATIO = (function () { var ctx = document.createElement("canvas").getContext("2d"), dpr = window.devicePixelRatio || 1, bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; return dpr / bsr; })(); setHiDPICanvas = function(canvas, w, h, ratio) { if (!ratio) { ratio = PIXEL_RATIO; } var can = canvas; can.width = w * ratio; can.height = h * ratio; can.style.width = w + "px"; can.style.height = h + "px"; can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0); } var svg = document.querySelector('#chart svg'), canvas = document.querySelector('#chart canvas'); var svgSize = svg.getBoundingClientRect(); var w = svgSize.width, h = svgSize.height; setHiDPICanvas(canvas, w, h); var svgString = (new XMLSerializer).serializeToString(svg); var ctx = canvas.getContext('2d'); ctx.drawSvg(svgString, 0, 0, w, h); }
źródło
Dla tych z Was, którzy pracują w actjs, dostosowałem odpowiedź MyNameIsKo i działa świetnie. Oto kod.
import React from 'react' export default class CanvasComponent extends React.Component { constructor(props) { this.calcRatio = this.calcRatio.bind(this); } // Use componentDidMount to draw on the canvas componentDidMount() { this.updateChart(); } calcRatio() { let ctx = document.createElement("canvas").getContext("2d"), dpr = window.devicePixelRatio || 1, bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; return dpr / bsr; } // Draw on the canvas updateChart() { // Adjust resolution const ratio = this.calcRatio(); this.canvas.width = this.props.width * ratio; this.canvas.height = this.props.height * ratio; this.canvas.style.width = this.props.width + "px"; this.canvas.style.height = this.props.height + "px"; this.canvas.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0); const ctx = this.canvas.getContext('2d'); // now use ctx to draw on the canvas } render() { return ( <canvas ref={el=>this.canvas=el} width={this.props.width} height {this.props.height}/> ) } }
W tym przykładzie podaję szerokość i wysokość płótna jako rekwizyty.
źródło
Wypróbuj ten jeden wiersz CSS na swoim płótnie:
image-rendering: pixelated
Zgodnie z MDN :
W ten sposób całkowicie zapobiega wygładzaniu krawędzi.
źródło
Chociaż odpowiedź @ MyNameIsKo nadal działa, jest teraz trochę przestarzała w 2020 roku i można ją ulepszyć:
function createHiPPICanvas(w, h) { let ratio = window.devicePixelRatio; let cv = document.createElement("canvas"); cv.width = w * ratio; cv.height = h * ratio; cv.style.width = w + "px"; cv.style.height = h + "px"; cv.getContext("2d").scale(ratio, ratio); return cv; }
Ogólnie wprowadzamy następujące ulepszenia:
backingStorePixelRatio
odniesienia, ponieważ nie są one tak naprawdę zaimplementowane w żadnej przeglądarce w żaden istotny sposób (w rzeczywistości tylko Safari zwraca coś innego niżundefined
, a ta wersja nadal działa doskonale w Safari);window.devicePixelRatio
, który ma fantastyczne wsparcie|| 1
fallback nawindow.devicePixelRatio
, jak to jest bez sensu: wszystkich przeglądarek, które nie obsługują tej właściwości nie obsługuje.setTransform
lub.scale
też, więc tej funkcji nie będzie działać na nich, awaryjna, czy nie;.setTransform
przez.scale
, ponieważ podanie szerokości i wysokości jest nieco bardziej intuicyjne niż przekazanie macierzy transformacji.createHiDPICanvas
nacreateHiPPICanvas
. Jak sam @MyNameIsKo wspomina w swoich komentarzach, DPI (Dots per Inch) drukuje terminologię (ponieważ drukarki tworzą obrazy z małych kropek kolorowego atramentu). Chociaż podobnie, monitory wyświetlają obrazy za pomocą pikseli i jako takie PPI (piksele na cal) jest lepszym akronimem dla naszego przypadku użycia.Dużą zaletą tych uproszczeń jest to, że ta odpowiedź może być teraz używana w TypeScript bez
// @ts-ignore
(ponieważ TS nie ma typów dlabackingStorePixelRatio
).źródło
W moim przypadku tylko połączenie różnych technik „pixel perfect” pomogło zarchiwizować wyniki:
Pobierz i skaluj ze współczynnikiem pikseli zgodnie z sugestią @MyNameIsKo.
pixelRatio = window.devicePixelRatio / ctx.backingStorePixelRatio
Skaluj płótno przy zmianie rozmiaru (unikaj domyślnego rozciągania płótna).
pomnóż lineWidth za pomocą pixelRatio, aby znaleźć właściwą `` rzeczywistą '' grubość linii w pikselach:
context.lineWidth = grubość * pixelRatio;
Sprawdź, czy grubość linii jest nieparzysta czy parzysta. dodaj połowę pixelRatio do pozycji linii dla nieparzystych wartości grubości.
x = x + pixelRatio / 2;
Nieparzysta linia zostanie umieszczona na środku piksela. Powyższa linia służy do jego niewielkiego przesunięcia.
function getPixelRatio(context) { dpr = window.devicePixelRatio || 1, bsr = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; return dpr / bsr; } var canvas = document.getElementById('canvas'); var context = canvas.getContext("2d"); var pixelRatio = getPixelRatio(context); var initialWidth = canvas.clientWidth * pixelRatio; var initialHeight = canvas.clientHeight * pixelRatio; window.addEventListener('resize', function(args) { rescale(); redraw(); }, false); function rescale() { var width = initialWidth * pixelRatio; var height = initialHeight * pixelRatio; if (width != context.canvas.width) context.canvas.width = width; if (height != context.canvas.height) context.canvas.height = height; context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); } function pixelPerfectLine(x) { context.save(); context.beginPath(); thickness = 1; // Multiple your stroke thickness by a pixel ratio! context.lineWidth = thickness * pixelRatio; context.strokeStyle = "Black"; context.moveTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 0)); context.lineTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 200)); context.stroke(); context.restore(); } function pixelPerfectRectangle(x, y, w, h, thickness, useDash) { context.save(); // Pixel perfect rectange: context.beginPath(); // Multiple your stroke thickness by a pixel ratio! context.lineWidth = thickness * pixelRatio; context.strokeStyle = "Red"; if (useDash) { context.setLineDash([4]); } // use sharp x,y and integer w,h! context.strokeRect( getSharpPixel(thickness, x), getSharpPixel(thickness, y), Math.floor(w), Math.floor(h)); context.restore(); } function redraw() { context.clearRect(0, 0, canvas.width, canvas.height); pixelPerfectLine(50); pixelPerfectLine(120); pixelPerfectLine(122); pixelPerfectLine(130); pixelPerfectLine(132); pixelPerfectRectangle(); pixelPerfectRectangle(10, 11, 200.3, 443.2, 1, false); pixelPerfectRectangle(41, 42, 150.3, 443.2, 1, true); pixelPerfectRectangle(102, 100, 150.3, 243.2, 2, true); } function getSharpPixel(thickness, pos) { if (thickness % 2 == 0) { return pos; } return pos + pixelRatio / 2; } rescale(); redraw();
canvas { image-rendering: -moz-crisp-edges; image-rendering: -webkit-crisp-edges; image-rendering: pixelated; image-rendering: crisp-edges; width: 100vh; height: 100vh; }
<canvas id="canvas"></canvas>
Zdarzenie zmiany rozmiaru nie jest uruchamiane w wycinanym pliku, więc możesz wypróbować plik na githubie
źródło
Dla mnie to nie tylko obraz, ale i tekst miał kiepską jakość. Najprostszym rozwiązaniem działającym w różnych przeglądarkach dla wyświetlaczy Retina / non-Retina było renderowanie obrazu dwukrotnie większego niż zamierzano i skalowanie kontekstu płótna, jak sugerował ten facet: https://stackoverflow.com/a/53921030/4837965
źródło