Eu sou um total n00b com HTML5
e estou trabalhando com a canvas
prestar formas, cores, e texto. Em meu aplicativo, tenho um adaptador de visualização que cria uma tela dinamicamente e a preenche com conteúdo. Isso funciona muito bem, exceto que meu texto fica muito confuso / borrado / esticado. Já vi muitos outros posts sobre por que definir a largura e a altura em CSS
causará esse problema, mas eu defino tudo em javascript
.
O código relevante (ver 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>
Os resultados que estou vendo (no Safari ) são muito mais distorcidos do que os mostrados no Fiddle:
Meu
Violino
O que estou fazendo incorretamente? Eu preciso de uma tela separada para cada elemento de texto? É a fonte? Devo primeiro definir a tela no layout HTML5? Existe um erro de digitação? Eu estou perdido.
fonte
clearRect
.Respostas:
O elemento canvas é executado independentemente da proporção de pixels do dispositivo ou monitor.
No iPad 3+, essa proporção é 2. Isso significa essencialmente que sua tela de largura de 1000 px agora precisa preencher 2.000 px para corresponder à largura declarada na tela do iPad. Felizmente para nós, isso é feito automaticamente pelo navegador. Por outro lado, esta também é a razão pela qual você vê menos definição em imagens e elementos de tela que foram feitos para caber diretamente em sua área visível. Como sua tela sabe apenas como preencher 1000px, mas é solicitada a desenhar em 2000px, o navegador deve agora preencher de forma inteligente os espaços em branco entre os pixels para exibir o elemento em seu tamanho adequado.
Eu recomendo fortemente que você leia este artigo do HTML5Rocks que explica em mais detalhes como criar elementos de alta definição.
tl; dr? Aqui está um exemplo (com base no tut acima) que uso em meus próprios projetos para cuspir uma tela com a resolução adequada:
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);
Espero que isto ajude!
fonte
Resolvido!
Decidi ver o que alterava os atributos de largura e altura que defini
javascript
para ver como isso afetava o tamanho da tela - e não afetou. Isso muda a resolução.Para obter o resultado que queria, também tive que definir o
canvas.style.width
atributo, que altera o tamanho físico docanvas
: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
fonte
window.devicePixelRatio
, e é bem implementado na maioria dos navegadores modernos.Eu redimensiono o elemento canvas via css para ocupar toda a largura do elemento pai. Percebi que a largura e a altura do meu elemento não são dimensionadas. Eu estava procurando a melhor maneira de definir o tamanho que deveria ser.
Desta forma simples, sua tela será configurada perfeitamente, não importa qual tela você usará.
fonte
Isso resolveu 100% para mim:
var canvas = document.getElementById('canvas'); canvas.width = canvas.getBoundingClientRect().width; canvas.height = canvas.getBoundingClientRect().height;
(está próximo da solução de Adam Mańkowski).
fonte
Adaptei um pouco o código MyNameIsKo em canvg (SVG para a biblioteca js do Canvas). Fiquei confuso por um tempo e gastei algum tempo com isso. Espero que isso ajude alguém.
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); }
fonte
Notei um detalhe não mencionado nas outras respostas. A resolução da tela trunca para valores inteiros.
As dimensões de resolução padrão da tela são
canvas.width: 300
ecanvas.height: 150
.Na minha tela
window.devicePixelRatio: 1.75
,.Portanto, quando eu defino
canvas.height = 1.75 * 150
o valor é truncado do desejado262.5
para baixo262
.Uma solução é escolher as dimensões do layout CSS para um dado
window.devicePixelRatio
, de forma que o truncamento não ocorra ao dimensionar a resolução.Por exemplo, eu poderia usar
width: 300px
eheight: 152px
que resultaria em números inteiros quando multiplicados por1.75
.Edit : Outra solução é tirar vantagem do fato de que os pixels CSS podem ser fracionários para neutralizar o truncamento dos pixels da tela de escala.
Abaixo está uma demonstração usando essa estratégia.
Edit : Aqui está o violino do OP atualizado para usar essa estratégia: 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>
fonte
Para aqueles de vocês que trabalham em reactjs, adaptei a resposta de MyNameIsKo e funciona muito bem. Aqui está o código.
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}/> ) } }
Neste exemplo, passo a largura e a altura da tela como adereços.
fonte
Experimente esta linha de CSS em sua tela:
image-rendering: pixelated
Conforme MDN :
Assim, impede totalmente o anti-aliasing.
fonte
Para mim, apenas uma combinação de diferentes técnicas de 'pixelização perfeita' ajudou a arquivar os resultados:
Obtenha e dimensione com uma proporção de pixel como @MyNameIsKo sugerido.
pixelRatio = window.devicePixelRatio / ctx.backingStorePixelRatio
Dimensione a tela no redimensionamento (evite o dimensionamento esticado padrão da tela).
multiplique lineWidth com pixelRatio para encontrar a espessura de linha de pixel 'real' adequada:
context.lineWidth = thick * pixelRatio;
Verifique se a espessura da linha é ímpar ou par. adicione metade de pixelRatio à posição da linha para os valores de espessura ímpares.
x = x + pixelRatio / 2;
A linha ímpar será colocada no meio do pixel. A linha acima é usada para movê-lo um pouco.
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>
O evento de redimensionamento não é disparado no recorte, então você pode tentar o arquivo no github
fonte
Para mim não era apenas a imagem, mas o texto tinha má qualidade. A solução mais simples de trabalho em vários navegadores para telas retina / não retina era renderizar a imagem com o dobro do tamanho pretendido e dimensionar o contexto da tela, como esse cara sugeriu: https://stackoverflow.com/a/53921030/4837965
fonte