- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我遇到了可变字体的问题,想知道是否有人有解决方案。我已经使用可变字体构建了这个海报生成器,您可以在其中操纵两个轴上的字体变化设置。这是一个活生生的例子 http://automat.markjulienhahn.de
现在我正在尝试通过 html2canvas 下载结果。不幸的是, Canvas 对象似乎不支持可变字体,因此 Canvas 对象只能显示字体的一种状态,而 fontVariationSettings 没有任何效果。
这就是我拉 Canvas 元素的方式:
<script src="html2canvas.min.js"></script>
<script>
var app = new Vue({
el: '#app',
methods: {
saveCanvas(){
html2canvas(document.querySelector("#capture")).then(
canvas => {
document.body.appendChild(canvas);
var image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
console.log(image);
window.location.href=image;
});
}
}
})
</script>
这就是我操作可变字体的方式。
function randomizeState() {
randomWeight = Math.floor(Math.random(1,100) * 100);
randomWidth = Math.floor(Math.random(1,100) * 100);
document.getElementById("element").style.fontVariationSettings = "\"frst\" " + randomWeight + ", \"scnd\" " + randomWidth;
document.getElementById("state1").innerHTML = randomWeight + " " + randomWidth;
}
如有任何帮助,我将不胜感激!
最佳答案
不幸的是你是对的,我们目前不能直接在 Canvas 中使用可变字体。所以这使得 html2canvas 的 Canvas 渲染器无法正确渲染。
新版本的 html2canvas 带有 foreignObjectRenderer
,它使用 Canvas API 的能力绘制 SVG 图像,并结合 SVG 的能力在 <foreignObject>
中包含 HTML 元素。 .
这确实是我们必须在 Canvas 上绘制可变字体的唯一当前解决方案,但是要使其正常工作,需要将字体嵌入到将在 Canvas 上绘制的 svg 文档中。而且,html2canvas 不会为我们做这件事(尽管我最近没有检查,但我认为 DOM2image 等其他解决方案也不会这样做)。
所以我们必须自己做。
data://
URL,以便它可以存在于独立的 svg 文件中。<foreignObject>
元素和我们元素的副本以及它们所需的计算样式。<foreignObject>
构建 svg 图像和一个 <style>
从 data://
声明我们的字体URL,并将其绘制在 Canvas 上。(async () => {
const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS( svgNS, "svg" );
const font_data = await fetchAsDataURL( "https://fonts.gstatic.com/s/inter/v2/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2" );
const style = document.createElementNS( svgNS, "style" );
style.textContent = `@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200 900;
src: url(${ font_data }) format('woff2');
}`;
svg.append( style );
const foreignObject = document.createElementNS( svgNS, "foreignObject" );
foreignObject.setAttribute( "x", 0 );
foreignObject.setAttribute( "y", 0 );
const target = document.querySelector( ".target" );
const clone = cloneWithStyles( target );
foreignObject.append( clone );
const { width, height } = target.getBoundingClientRect();
foreignObject.setAttribute( "width", width );
foreignObject.setAttribute( "height", height );
svg.setAttribute( "width", width );
svg.setAttribute( "height", height );
svg.append( foreignObject );
const svg_markup = new XMLSerializer().serializeToString( svg );
const svg_file = new Blob( [ svg_markup ], { type: "image/svg+xml" } );
const img = new Image();
img.src = URL.createObjectURL( svg_file );
await img.decode();
URL.revokeObjectURL( img.src );
const canvas = document.createElement( "canvas" );
Object.assign( canvas, { width, height } );
const ctx = canvas.getContext( "2d" );
ctx.drawImage( img, 0, 0 );
document.body.append( canvas );
})().catch( console.error );
function fetchAsDataURL( url ) {
return fetch( url )
.then( (resp) => resp.ok && resp.blob() )
.then( (blob) => new Promise( (res) => {
const reader = new FileReader();
reader.onload = (evt) => res( reader.result );
reader.readAsDataURL( blob );
} )
);
}
function cloneWithStyles( source ) {
const clone = source.cloneNode( true );
// to make the list of rules smaller we try to append the clone element in an iframe
const iframe = document.createElement( "iframe" );
document.body.append( iframe );
// if we are in a sandboxed context it may be null
if( iframe.contentDocument ) {
iframe.contentDocument.body.append( clone );
}
const source_walker = document.createTreeWalker( source, NodeFilter.SHOW_ELEMENT, null );
const clone_walker = document.createTreeWalker( clone, NodeFilter.SHOW_ELEMENT, null );
let source_element = source_walker.currentNode;
let clone_element = clone_walker.currentNode;
while ( source_element ) {
const source_styles = getComputedStyle( source_element );
const clone_styles = getComputedStyle( clone_element );
// we should be able to simply do [ ...source_styles.forEach( (key) => ...
// but thanks to https://crbug.com/1073573
// we have to filter all the snake keys from enumerable properties...
const keys = (() => {
// Start with a set to avoid duplicates
const props = new Set();
for( let prop in source_styles ) {
// Undo camel case
prop = prop.replace( /[A-Z]/g, (m) => "-" + m.toLowerCase() );
// Fix vendor prefix
prop = prop.replace( /^webkit-/, "-webkit-" );
props.add( prop );
}
return props;
})();
for( let key of keys ) {
if( clone_styles[ key ] !== source_styles[ key ] ) {
clone_element.style.setProperty( key, source_styles[ key ] );
}
}
source_element = source_walker.nextNode()
clone_element = clone_walker.nextNode()
}
// clean up
iframe.remove();
return clone;
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200 900;
src: url(https://fonts.gstatic.com/s/inter/v2/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2');
}
.t1 {
font-family: 'Inter';
font-variation-settings: 'wght' 200;
}
.t2 {
font-family: 'Inter';
font-variation-settings: 'wght' 900;
}
canvas {
border: 1px solid;
}
<div class="target">
<span class="t1">
Hello
</span>
<span class="t2">
World
</span>
</div>
关于javascript - html5-canvas 中的可变字体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65670959/
为什么禁用类型像 type t = A of int | B of string * mutable int 虽然允许此类类型: type t = A of int | B of string * i
我正在寻找一种类似结构的数据结构,我可以从中创建多个实例并具有某种类型提示而不是不可变的。 所以我有这样的东西: class ConnectionConfig(NamedTuple): nam
我需要转到引用的结构: class SearchKnot { var isWord : Bool = false var text : String = "" var to
如sec 10.4.3中所述 当控制进入执行时,执行以下步骤 功能对象F(调用者)中包含的功能代码的上下文 提供thisArg,而调用方提供argumentsList: 如
i make a game that start display Activity indicator And activity indicator bottom display UiLable wi
编辑:我在这里不断获得支持。只是为了记录,我认为这不再重要。自从我发布它以来我就不再需要它了。 我想在 Scala 中执行以下操作... def save(srcPath: String, destP
使用可变对象作为 Hashmap 键是一种不好的做法吗?当您尝试使用已修改足以更改其哈希码的键从 HashMap 中检索值时,会发生什么? 例如,给定 class Key { int a; /
如果您在Kotlin中访问List类型的Java值,则将获得(Mutable)List!类型。 例如。: Java代码: public class Example { public stati
我编写了 str 类(内置)的以下扩展,以便执行以下操作:假设我有字符串 "Ciao" ,通过做"Ciao" - "a"我想要的结果是字符串 "Cio" 。这是执行此操作的代码,并且运行良好: cla
使用可变对象作为 Hashmap 键是一种不好的做法吗?当您尝试使用已修改足以更改其哈希码的键从 HashMap 中检索值时,会发生什么? 例如,给定 class Key { int a; /
我正在为我的公司设计一个数据库来管理商业贷款。每笔贷款都可以有担保人,可以是个人或公司,在借款业务失败时作为财务支持。 我有 3 个表:Loan、Person 和 Company,它们存储明显的信息。
我使用二进制序列化从 C# 类中保存 F# 记录。一切正常: F#: type GameState = { LevelStatus : LevelStatus
import javax.swing.JOptionPane; public class HW { public static void main(String[] args) { Strin
使用 flatbuffer mutable 有多少性能损失? 是否“正确”使用 FlatBuffers 来拥有一个应该可编辑的对象/结构(即游戏状态) 在我的示例中,我现在有以下类: class Ga
std::function create_function (args...) { int x = initial_value (args...); return [x] () mut
我需要在 for 循环中找到用户输入的字符。我通常会这样做 如果(句子[i] == 'e') 但是因为在这里,'e' 将是一个单字母字符变量,我不知道如何获取要比较的值。我不能只输入 if (sent
我有一个这样的算法: let seed: Foo = ... let mut stack: Vec = Vec::new(); stack.push(&seed); while let Some(ne
这个问题可能看起来非常基础,但我很难弄清楚如何做。我有一个整数,我需要使用 for 循环来循环整数次。 首先,我尝试了—— fn main() { let number = 10; // An
如果我有以下结构: struct MyStruct { tuple: (i32, i32) }; 以及以下函数: // This will not compile fn function(&mut s
我希望在每个 session 的基础上指定列的默认值。下面的脚本不起作用,但描述了我想如何使用它。我目前使用的是 MySQL 5.5.28,但如果需要可以升级。 CREATE TABLE my_tbl
我是一名优秀的程序员,十分优秀!