Làm cách nào để sửa văn bản bị mờ trong canvas HTML5 của tôi?


87

Tôi là một tổng n00b với HTML5và đang làm việc với canvasđể render hình dạng, màu sắc và văn bản. Trong ứng dụng của mình, tôi có bộ điều hợp chế độ xem có thể tạo canvas động và lấp đầy nội dung. Điều này hoạt động thực sự độc đáo, ngoại trừ việc văn bản của tôi được hiển thị rất mờ / mờ / kéo dài. Tôi đã thấy rất nhiều bài đăng khác về lý do tại sao việc xác định chiều rộngchiều cao trong CSSsẽ gây ra vấn đề này, nhưng tôi xác định tất cả trong javascript.

Mã liên quan (xem 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>

Kết quả tôi đang thấy (trong Safari ) bị lệch nhiều hơn so với hiển thị trong Fiddle:

Của tôi

Kết xuất đầu ra trong Safari

Vĩ cầm

Kết xuất đầu ra trên JSFiddle

Tôi đang làm gì sai? Tôi có cần một canvas riêng cho từng phần tử văn bản không? Nó có phải là phông chữ không? Trước tiên, tôi có bắt buộc phải xác định canvas trong bố cục HTML5 không? Có một lỗi chính tả? Tôi bị lạc.


Có vẻ như bạn không gọi clearRect.
David

1
Đây sửa polyfill hoạt động canvas cơ bản nhất với các trình duyệt HiDPI mà không làm tự động cao cấp (hiện tại săn là người duy nhất) ... github.com/jondavidjohn/hidpi-canvas-polyfill
jondavidjohn

Tôi đang phát triển một khuôn khổ JS có thể giải quyết vấn đề làm mờ canvas với DIV mosaic. Tôi tạo ra một hình ảnh rõ ràng và sắc nét hơn tại một số chi phí về mem / cpu js2dx.com
Gonki

Câu trả lời:


155

Phần tử canvas chạy độc lập với tỷ lệ pixel của thiết bị hoặc màn hình.

Trên iPad 3+, tỷ lệ này là 2. Về cơ bản, điều này có nghĩa là canvas chiều rộng 1000px của bạn bây giờ sẽ cần phải lấp đầy 2000px để phù hợp với chiều rộng đã nêu trên màn hình iPad. May mắn thay cho chúng tôi, điều này được thực hiện tự động bởi trình duyệt. Mặt khác, đây cũng là lý do tại sao bạn nhìn thấy ít độ nét hơn trên hình ảnh và các phần tử canvas được tạo ra để phù hợp trực tiếp với vùng nhìn thấy của chúng. Bởi vì canvas của bạn chỉ biết cách lấp đầy 1000px nhưng được yêu cầu vẽ đến 2000px, trình duyệt hiện phải điền thông minh vào các khoảng trống giữa các pixel để hiển thị phần tử ở kích thước phù hợp.

Tôi thực sự khuyên bạn nên đọc bài viết này từ HTML5Rocks , bài viết này giải thích chi tiết hơn cách tạo các phần tử độ nét cao.

tl; dr? Đây là một ví dụ (dựa trên tut ở trên) mà tôi sử dụng trong các dự án của riêng mình để tạo ra một canvas với độ phân giải thích hợp:

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);

Hi vọng điêu nay co ich!


2
Tôi nghĩ rằng điều đáng nói là phương thức createHiDPI () của tôi được đặt tên hơi kém. DPI là thuật ngữ chỉ bản in và PPI là từ viết tắt thích hợp hơn vì màn hình hiển thị hình ảnh bằng cách sử dụng pixel thay vì chấm.
MyNameIsKo

3
Lưu ý rằng tỷ lệ này thực sự có thể thay đổi trong suốt thời gian tồn tại của trang. Ví dụ: nếu tôi kéo cửa sổ Chrome từ màn hình ngoài res "chuẩn" cũ hơn sang màn hình retina tích hợp sẵn của macbook, mã sẽ tính toán một tỷ lệ khác. Chỉ là FYI nếu bạn định lưu giá trị này vào bộ nhớ cache. (bên ngoài là tỷ lệ 1, võng mạc màn hình 2 trong trường hợp bạn đang tò mò)
Aardvark

1
Cảm ơn vì lời giải thích này. Nhưng làm thế nào về nội dung hình ảnh? Chúng ta có cần cung cấp mọi hình ảnh canvas ở độ phân giải gấp đôi và thu nhỏ theo cách thủ công không?
Kokodoko

1
Thông tin bên lề khác: IE của Windows Phone 8 luôn báo cáo 1 cho window.devicePixelRatio (và lệnh gọi pixel sao lưu không hoạt động). Tỷ lệ 1 trông rất tệ, nhưng tỷ lệ 2 có vẻ tốt. Hiện tại, các phép tính tỷ lệ của tôi luôn trả về ít nhất là 2 (cách giải quyết tồi tệ, nhưng nền tảng mục tiêu của tôi là điện thoại hiện đại gần như tất cả đều có màn hình DPI cao). Đã thử nghiệm trên HTC 8X và Lumia 1020.
Aardvark

1
@Aardvark: cũng có vẻ như msBackingStorePixelRatio luôn không được xác định. Đã hỏi một câu hỏi mới về vấn đề đó tại đây: stackoverflow.com/questions/22483296/…
TV's Frank

28

Đã giải quyết!

Tôi quyết định xem điều gì thay đổi các thuộc tính chiều rộngchiều cao mà tôi đã đặtjavascript để xem điều đó ảnh hưởng như thế nào đến kích thước canvas - và điều đó đã không. Nó thay đổi độ phân giải.

Để có được kết quả tôi muốn, tôi cũng phải đặt canvas.style.widththuộc tính, thuộc tính này sẽ thay đổi kích thước vật lý của canvas:

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

1
Để có kết quả lý tưởng, bạn không nên chạm vào các thuộc tính kiểu của canvas và chỉ kiểm soát kích thước bằng các thuộc tính chiều rộng và chiều cao của chính canvas. Bằng cách đó, bạn đảm bảo rằng một pixel trên canvas bằng một pixel trên màn hình.
Philipp

7
Tôi không đồng ý. Thay đổi các thuộc tính style.width / height chính là cách bạn tạo canvas HiDPI.
MyNameIsKo

2
Trong câu trả lời của bạn, bạn đặt canvas.width thành 1000 và canvas.style.width thành một nửa ở 500. Điều này hoạt động nhưng chỉ đối với thiết bị có tỷ lệ pixel là 2. Đối với bất kỳ thứ gì dưới đây, chẳng hạn như màn hình máy tính để bàn của bạn, canvas hiện là vẽ đến các pixel không cần thiết. Đối với các tỷ lệ cao hơn, bạn hiện đã trở lại ngay từ đầu với phần tử / nội dung mờ, độ phân giải thấp. Một vấn đề khác mà Philipp dường như đang ám chỉ là mọi thứ bạn vẽ vào ngữ cảnh của mình bây giờ phải được vẽ theo chiều rộng / chiều cao gấp đôi của bạn mặc dù nó đang được hiển thị ở một nửa giá trị đó. Cách khắc phục là đặt ngữ cảnh của canvas của bạn thành gấp đôi.
MyNameIsKo

2
window.devicePixelRatio, và nó được triển khai tốt trong hầu hết các trình duyệt hiện đại.
Chen

6

Tôi thay đổi kích thước phần tử canvas thông qua css để lấy toàn bộ chiều rộng của phần tử mẹ. Tôi nhận thấy rằng chiều rộng và chiều cao của phần tử của tôi không được chia tỷ lệ. Tôi đang tìm cách tốt nhất để đặt kích thước nên được.

canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

Cách đơn giản này canvas của bạn sẽ được thiết lập hoàn hảo, bất kể bạn sẽ sử dụng màn hình nào.


5

Điều này 100% đã giải quyết nó cho tôi:

var canvas = document.getElementById('canvas');
canvas.width = canvas.getBoundingClientRect().width;
canvas.height = canvas.getBoundingClientRect().height;

(nó gần với giải pháp của Adam Mańkowski).


3

Tôi đã điều chỉnh một chút mã MyNameIsKo dưới canvg (SVG thành thư viện Canvas js). Tôi đã bối rối một lúc và dành một chút thời gian cho việc này. Hy vọng điều này sẽ giúp ai đó.

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);

}

3

Tôi nhận thấy một chi tiết không được đề cập trong các câu trả lời khác. Độ phân giải canvas cắt ngắn thành giá trị số nguyên.

Kích thước độ phân giải canvas mặc định là canvas.width: 300canvas.height: 150 .

Trên màn hình của tôi, window.devicePixelRatio: 1.75 ,.

Vì vậy, khi tôi đặt canvas.height = 1.75 * 150giá trị bị cắt bớt từ mong muốn 262.5xuống262 .

Một giải pháp là chọn kích thước bố cục CSS cho một window.devicePixelRatio để việc cắt ngắn sẽ không xảy ra khi mở rộng độ phân giải.

Ví dụ, tôi có thể sử dụng width: 300pxheight: 152pxsẽ mang lại số nguyên khi nhân với 1.75.

Chỉnh sửa : Một giải pháp khác là tận dụng lợi thế của thực tế là các pixel CSS có thể là phân số để chống lại việc cắt bớt các pixel canvas chia tỷ lệ.

Dưới đây là bản demo sử dụng chiến lược này.

Chỉnh sửa : Đây là fiddle của OP được cập nhật để sử dụng chiến lược này: 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>


2

Đối với những bạn làm việc trong reactjs, tôi đã điều chỉnh câu trả lời của MyNameIsKo và nó hoạt động rất tốt. Đây là mã.

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}/>
        )
    }
}

Trong ví dụ này, tôi chuyển chiều rộng và chiều cao của canvas làm đạo cụ.


2

Hãy thử một dòng CSS này trên canvas của bạn: image-rendering: pixelated

Theo MDN :

Khi tăng tỷ lệ hình ảnh, thuật toán láng giềng gần nhất phải được sử dụng để hình ảnh dường như bao gồm các pixel lớn.

Vì vậy, nó ngăn chặn hoàn toàn khử răng cưa.


0

Đối với tôi, chỉ có sự kết hợp của các kỹ thuật 'pixel hoàn hảo' khác nhau mới giúp lưu trữ kết quả:

  1. Nhận và chia tỷ lệ với tỷ lệ pixel như @MyNameIsKo đề xuất.

    pixelRatio = window.devicePixelRatio / ctx.backingStorePixelRatio

  2. Chia tỷ lệ canvas trên thay đổi kích thước (tránh giãn tỷ lệ mặc định canvas).

  3. nhiều lineWidth với pixelRatio để tìm độ dày đường pixel 'thực' thích hợp:

    context.lineWidth = height * pixelRatio;

  4. Kiểm tra độ dày của vạch là lẻ hay chẵn. thêm một nửa pixelRatio vào vị trí đường đối với các giá trị độ dày lẻ.

    x = x + pixelRatio / 2;

Dòng lẻ sẽ được đặt ở giữa pixel. Dòng trên được sử dụng để di chuyển nó một chút.

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>

Sự kiện thay đổi kích thước không được kích hoạt trong đoạn trích, vì vậy bạn có thể thử tệp trên github


0

Đối với tôi, đó không chỉ là hình ảnh mà cả văn bản đều có chất lượng tồi. Giải pháp làm việc trên nhiều trình duyệt đơn giản nhất cho màn hình retina / non-retina là hiển thị hình ảnh lớn gấp đôi so với dự định và mở rộng ngữ cảnh canvas như anh chàng này đã đề xuất: https://stackoverflow.com/a/53921030/4837965

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.