Você pode controlar como a largura do traçado de um SVG é desenhada?

204

Atualmente construindo um aplicativo SVG baseado em navegador. Dentro deste aplicativo, várias formas podem ser estilizadas e posicionadas pelo usuário, incluindo retângulos.

Quando aplico a stroke-widthem um rectelemento SVG , por exemplo 1px, o traçado é aplicado ao rectdeslocamento e inserção do de diferentes maneiras por diferentes navegadores. Isso está se mostrando problemático, especialmente quando tento calcular a largura externa e a posição visual de um retângulo e posicioná-lo ao lado de outros elementos.

Por exemplo:

  • O Firefox adiciona 1 px inserido (inferior e esquerdo) e 1 px offset (superior e direito)
  • O Chrome adiciona 1 px inserido (superior e esquerdo) e 1 px offset (inferior e direito)

Minha única solução até agora seria desenhar as bordas reais (provavelmente com a pathferramenta) e posicionar as bordas atrás do elemento traçado. Mas essa solução é uma solução desagradável e eu preferiria não seguir esse caminho, se possível.

Então, minha pergunta é: você pode controlar como um SVG stroke-widthé desenhado em elementos?

Steve
fonte
existem hacks filtro que você pode usar para conseguir isso - mas não é uma grande solução
Michael Mullany
2
Há o paint-orderparâmetro, onde você pode especificar, que o preenchimento deve ser renderizado na parte superior do traçado, para que você obtenha o "alinhamento externo", consulte jsfiddle.net/hne0kyLg/1
Ivan Kuckir
Encontrei uma maneira de fazer isso usando os atributos 'esboço-' do css: codepen.io/badcat/pen/YVzmYY . Não sei qual é o suporte para isso nos navegadores, mas pode ser útil.
Bplmp # 5/10

Respostas:

375

Não, você não pode especificar se o traçado é desenhado dentro ou fora de um elemento. Fiz uma proposta ao grupo de trabalho SVG para essa funcionalidade em 2003, mas ele não recebeu suporte (ou discussão).

Exemplo de localização de traçado proposto pelo SVG, em phrogz.net/SVG/stroke-location.svg

Como observei na proposta,

  • você pode obter o mesmo resultado visual que "dentro" dobrando a largura do traçado e, em seguida, usando um traçado de recorte para prender o objeto nele mesmo, e
  • você pode obter o mesmo resultado visual de 'fora' dobrando a largura do traçado e, em seguida, sobrepondo uma cópia sem traço do objeto em cima dele.

Editar : Esta resposta pode estar errada no futuro. Deve ser possível alcançar esses resultados usando o SVG Vector Effects , combinando veStrokePathcom veIntersect(para 'dentro') ou com veExclude(para 'fora). No entanto, o Vector Effects ainda é um módulo de rascunho de trabalho sem implementações que ainda posso encontrar.

Edit 2 : A especificação de rascunho do SVG 2 inclui uma stroke-alignmentpropriedade (com o centro | dentro | fora dos valores possíveis). Essa propriedade poderá entrar em UAs eventualmente.

Editar 3 : Amusingly e dissapointingly, o grupo de trabalho SVG removeu stroke-alignmenta partir SVG 2. Você pode ver algumas das preocupações descritas após a prosa aqui .

Phrogz
fonte
2
Pensei que poderia ser o caso. Para resolver isso, escrevi uma função de wrapper para getBBox () chamada getStrokedBBox (). Esse invólucro retorna o BBox de acordo com a forma como o navegador aplica traçados à inserção e deslocamento de uma forma. Não é perfeito (precisa verificar as versões mais recentes do navegador), mas fornece com precisão uma largura externa de formas por enquanto.
Steve Steve
6
@Phrogz talvez o vejamos antes de 10 anos se passaram. svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint anotação
frenchone
1
Finalmente chegou! Eu, por exemplo, realmente pedi que esta propriedade chegasse.
mnsth
Eu criei um script svg-contour para rastrear o contorno de qualquer SVGGeometryElement que possa ser usado como uma solução alternativa para o alinhamento de traços. Para qualquer pessoa interessada, você pode encontrar uma descrição aqui
maioman
2
Existe alguma página em que eu possa votar novamente na proposta? Obrigado. Parece ridículo, não é suportado.
mahish
57

ATUALIZAÇÃO: o stroke-alignmentatributo foi no dia 1º de abril de 2015 movido para uma especificação completamente nova chamada SVG Strokes .

A partir do rascunho do Editor do SVG 2.0 de 26 de fevereiro de 2015 (e possivelmente desde 13 de fevereiro ),a stroke-alignmentpropriedade está presentecom os valores inner, center (padrão) e outer.

Parece funcionar da mesma maneira que a stroke-locationpropriedade proposta por @Phrogz e a stroke-positionsugestão posterior . Esta propriedade foi planejada desde pelo menos 2011, mas além de uma anotação que dizia

O SVG 2 deve incluir uma maneira de especificar a posição do curso

, nunca foi detalhado nas especificações como foi adiado - até agora, parece.

Nenhum navegador suporta essa propriedade, ou, até onde eu sei, qualquer um dos novos recursos do SVG 2, ainda, mas espero que eles o façam logo que a especificação amadurecer. Esta tem sido uma propriedade que eu pessoalmente tenho pedido, e estou muito feliz por finalmente estar lá nas especificações.

Parece haver algumas questões sobre como a propriedade deve se comportar em caminhos abertos e em loops. Esses problemas provavelmente prolongarão as implementações nos navegadores. No entanto, atualizarei esta resposta com novas informações à medida que os navegadores começarem a oferecer suporte a essa propriedade.

mnsth
fonte
1
stroke-alignmenté especificado em SVG Strokes, um rascunho de trabalho do W3C. Enquanto isso, o Rascunho do Editor SVG 2 W3C diz que uma propriedade de posicionamento de traçado deve estar na especificação SVG em svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint , mas essa especificação já atingiu o status de Recomendação do candidato do W3C e nenhuma propriedade é nas especificações, além de um link para a stroke-positionproposta, fazendo parecer que não será o caso.
Patrick Dark
47

Encontrei uma maneira fácil, que tem algumas restrições, mas funcionou para mim:

  • definir a forma em defs
  • definir um caminho de clipe referenciando a forma
  • use-o e dobre o curso com o exterior cortado

Aqui está um exemplo de trabalho:

<svg width="240" height="240" viewBox="0 0 1024 1024">
<defs>
	<path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
	<clipPath id="clip">
		<use xlink:href="#ld"/>
	</clipPath>
</defs>
<g>
	<use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="#00D2B8" clip-path="url(#clip)"/>
</g>
</svg>

Jorg Janke
fonte
2
A melhor resposta que encontrei
Ed Kolosovsky
1
Solução inteligente. Graças
Vince Yuan
Isso deve ser aceito como resposta. Usar <defs> e <use> é a solução mais elegante disponível atualmente.
precisa saber é o seguinte
Ótima solução. Como você o reverteria para fazer um golpe externo?
Lucent
Por que o nível superior <use>? Por que não colocar diretamente o caminho e o caminho do clipe? Isso funciona muito bem: <svg width="240" height="240" viewBox="0 0 1024 1024"> <path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z" clip-path="url(#clip)" stroke="#0081C6" stroke-width="160" fill="#00d2b8"/> <clipPath id="clip"> <use xlink:href="#ld"/> </clipPath> </svg>. (Sim, isso é completamente razoável e SVG correta e vai processar corretamente em todos os lugares tema nenhum recursão..)
Chris Morgan
30

Você pode usar CSS para estilizar a ordem dos traços e preenchimentos. Ou seja, toque primeiro e depois preencha em segundo, e obtenha o efeito desejado.

MDN em paint-order: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order

Código CSS:

paint-order: stroke;
Xavier Ho
fonte
Isto é perfeito! Obrigado por compartilhar
BrunoFenzl
1
A documentação vinculada parece não indicar se o traço está dentro, fora ou centralizado? Você poderia esclarecer como controlar para onde o traçado é desenhado? Obrigado.
Crashalot 19/11/19
2
O traçado está centralizado, como sempre foi - mas se você tiver um preenchimento sólido, o efeito de ajustar a ordem da tinta para pintar o traçado primeiro é que a metade interna do traçado é pintada, o que tem o mesmo efeito que desenhar um traço. golpe meio grosso como fora.
Chris Morgan
7

Aqui está uma função que calcula quantos pixels você precisa adicionar - usando o traçado especificado - nas partes superior, direita, inferior e esquerda, todos baseados no navegador:

var getStrokeOffsets = function(stroke){

        var strokeFloor =       Math.floor(stroke / 2),                                                                 // max offset
            strokeCeil =        Math.ceil(stroke / 2);                                                                  // min offset

        if($.browser.mozilla){                                                                                          // Mozilla offsets

            return {
                bottom:     strokeFloor,
                left:       strokeFloor,
                top:        strokeCeil,
                right:      strokeCeil
            };

        }else if($.browser.webkit){                                                                                     // WebKit offsets

            return {
                bottom:     strokeCeil,
                left:       strokeFloor,
                top:        strokeFloor,
                right:      strokeCeil
            };

        }else{                                                                                                          // default offsets

            return {
                bottom:     strokeCeil,
                left:       strokeCeil,
                top:        strokeCeil,
                right:      strokeCeil
            };

        }

    };
Steve
fonte
6

Como as pessoas acima observaram, você terá que recalcular um deslocamento nas coordenadas do caminho do traçado ou dobrar sua largura e depois mascarar um lado ou outro, porque o SVG não apenas não suporta nativamente o alinhamento do traçado do Illustrator, mas o PostScript também não. .

A especificação para traçados no PostScript da Adobe, 2a edição, afirma: " 4.5.1 Traçado: o operador traçado desenha uma linha com alguma espessura ao longo do caminho atual. Para cada segmento reto ou curvo no caminho, traçado traça uma linha centralizada em o segmento com lados paralelos ao segmento ". (ênfase deles)

O restante da especificação não possui atributos para compensar a posição da linha. Quando o Illustrator permite o alinhamento interno ou externo, ele recalcula o deslocamento do caminho real (porque ainda é computacionalmente mais barato do que sobreimprimir e mascarar). As coordenadas do caminho no documento .ai são referência, não o que é rasterizado ou exportado para um formato final.

Como o formato nativo do Inkscape é SVG de especificação, ele não pode oferecer um recurso que a especificação não possui.

user1574945
fonte
4

Aqui está uma solução alternativa para bordas internas rect usando symbole use.

Exemplo : https://jsbin.com/yopemiwame/edit?html,output

SVG :

<svg>
  <symbol id="inner-border-rect">
    <rect class="inner-border" width="100%" height="100%" style="fill:rgb(0,255,255);stroke-width:10;stroke:rgb(0,0,0)">
  </symbol>
  ...
  <use xlink:href="#inner-border-rect" x="?" y="?" width="?" height="?">
</svg>

Nota: Certifique-se de substituir o ?em usecom valores reais.

Antecedentes : A razão pela qual isto funciona é porque símbolo estabelece uma nova janela, substituindo symbolcom svge criando um elemento no DOM sombra. O svgDOM da sombra é então vinculado ao seu SVGelemento atual . Observe que svgs pode ser aninhado e cada svgum cria uma nova janela de exibição, que recorta tudo o que se sobrepõe, incluindo a borda sobreposta. Para uma visão muito mais detalhada do que está acontecendo, leia este fantástico artigo de Sara Soueidan.

F Lekschas
fonte
2

Não sei como isso será útil, mas no meu caso, criei outro círculo apenas com borda e coloquei "dentro" da outra forma.

A testemunha
fonte
0

Uma solução possível (suja) é usar padrões,

Aqui está um exemplo com um triângulo com traços internos:

https://jsfiddle.net/qr3p7php/5/

<style>
#triangle1{
  fill: #0F0;
  fill-opacity: 0.3;
  stroke: #000;
  stroke-opacity: 0.5;
  stroke-width: 20;
}
#triangle2{
  stroke: #f00;
  stroke-opacity: 1;
  stroke-width: 1;
}    
</style>

<svg height="210" width="400" >
    <pattern id="fagl" patternUnits="objectBoundingBox" width="2" height="1" x="-50%">
        <path id="triangle1" d="M150 0 L75 200 L225 200 Z">
    </pattern>    
    <path id="triangle2" d="M150 0 L75 200 L225 200 Z" fill="url(#fagl)"/>
</svg>
max-lt
fonte
0

A solução de Xavier Ho de dobrar a largura do traçado e alterar a ordem da tinta é brilhante, embora só funcione se o preenchimento for de uma cor sólida, sem transparência.

Eu desenvolvi outra abordagem, mais complicada, mas funciona para qualquer preenchimento. Também funciona em elipses ou caminhos (com o posterior há alguns casos de canto com comportamento estranho, por exemplo, caminhos abertos que se cruzam entre si, mas não muito).

O truque é exibir a forma em duas camadas. Um sem traço (preenchimento apenas) e outro apenas com traçado na largura dupla (preenchimento transparente) e passado por uma máscara que mostra toda a forma, mas oculta a forma original sem traçado.

  <svg width="240" height="240" viewBox="0 0 1024 1024">
  <defs>
    <path id="ld" d="M256,0 L0,512 L384,512 L128,1024 L1024,384 L640,384 L896,0 L256,0 Z"/>
    <mask id="mask">
      <use xlink:href="#ld" stroke="#FFFFFF" stroke-width="160" fill="#FFFFFF"/>
      <use xlink:href="#ld" fill="#000000"/>
    </mask>
  </defs>
  <g>
    <use xlink:href="#ld" fill="#00D2B8"/>
    <use xlink:href="#ld" stroke="#0081C6" stroke-width="160" fill="red" mask="url(#mask)"/>
  </g>
  </svg>
hirunatan
fonte